#ifdef so với #if - phương pháp nào tốt hơn / an toàn hơn để bật / tắt biên dịch các phần mã cụ thể?


112

Đây có thể là vấn đề về phong cách, nhưng có một chút chia rẽ trong đội ngũ nhà phát triển của chúng tôi và tôi tự hỏi liệu có ai khác có ý kiến ​​gì về vấn đề này không ...

Về cơ bản, chúng tôi có một số câu lệnh in gỡ lỗi mà chúng tôi tắt trong quá trình phát triển bình thường. Cá nhân tôi thích làm những điều sau:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

Mặc dù vậy, một số nhóm thích những điều sau:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

... phương pháp nào trong số đó có vẻ tốt hơn đối với bạn và tại sao? Cảm giác của tôi là cách đầu tiên an toàn hơn vì luôn có một cái gì đó được xác định và không có gì nguy hiểm khi nó có thể phá hủy các định nghĩa khác ở nơi khác.


Lưu ý: với #if, bạn cũng có thể sử dụng #eliftheo cách nhất quán, không giống như với#ifdef . Do đó, thay vì chỉ sử dụng #define BLAH, hãy sử dụng #define BLAH 1với #if BLAH, v.v.
Andrew

Câu trả lời:


82

#ifdefTất nhiên , phản ứng ban đầu của tôi là như vậy , nhưng tôi nghĩ #ifthực sự có một số lợi thế đáng kể cho việc này - đây là lý do tại sao:

Đầu tiên, bạn có thể sử dụng DEBUG_ENABLEDtrong các bài kiểm tra tiền xử lý biên dịch. Ví dụ - Thông thường, tôi muốn thời gian chờ lâu hơn khi bật gỡ lỗi, vì vậy #if, bằng cách sử dụng , tôi có thể viết

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... thay vì ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

Thứ hai, bạn đang ở một vị trí tốt hơn nếu bạn muốn chuyển từ một #definehằng số chung sang một hằng số toàn cầu. #defines thường được hầu hết các lập trình viên C ++ quan tâm.

Và, Thứ ba, bạn nói rằng bạn có sự chia rẽ trong nhóm của mình. Tôi đoán là điều này có nghĩa là các thành viên khác nhau đã áp dụng các cách tiếp cận khác nhau và bạn cần phải chuẩn hóa. Việc cai trị #iflà lựa chọn ưu tiên có nghĩa là mã sử dụng #ifdefsẽ biên dịch -và chạy- ngay cả khi DEBUG_ENABLEDsai. Và dễ dàng hơn nhiều để theo dõi và loại bỏ đầu ra gỡ lỗi được tạo ra khi nó không phải là ngược lại.

Ồ, và một điểm dễ đọc nhỏ. Bạn có thể sử dụng true / false thay vì 0/1 trong của bạn #definevà bởi vì giá trị là một mã thông báo từ vựng duy nhất, đó là lần duy nhất bạn không cần dấu ngoặc đơn xung quanh nó.

#define DEBUG_ENABLED true

thay vì

#define DEBUG_ENABLED (1)

Hằng số có thể không được sử dụng để bật / tắt gỡ lỗi, vì vậy việc kích hoạt #ifdef với #define thành 0 có thể không tốt cho lắm. Đối với true / false, chúng đã được thêm vào C99 và không tồn tại trong C89 / C90.
Michael Carman 25/09/08

Micheal: Anh ấy / cô ấy đang ủng hộ việc chống lại việc sử dụng #ifdef ?!
Jon Cage

7
Vâng, một vấn đề #ifdeflà nó hoạt động với những thứ chưa được xác định; cho dù chúng không được xác định một cách cố ý hay do lỗi đánh máy hay bạn có gì.
bames53

6
Bổ sung của bạn cho câu trả lời là sai. #if DEBUG_ENBALEDkhông phải là lỗi do bộ xử lý trước phát hiện. Nếu DEBUG_ENBALEDkhông được xác định, nó sẽ mở rộng thành mã thông báo 0trong #ifcác lệnh.
R .. GitHub NGỪNG TRỢ GIÚP LÚC NỮA.

6
@R .. Trong nhiều trình biên dịch, bạn có thể bật cảnh báo cho "#if DEBUG_ENABLED" khi DEBUG_ENABLED không được xác định. Trong GCC sử dụng "-Wundef". Trong Microsoft Visual Studio, hãy sử dụng "/ w14668" để bật C4668 làm cảnh báo mức 1.
Sẽ

56

Cả hai đều gớm ghiếc. Thay vào đó, hãy làm điều này:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

Sau đó, bất cứ khi nào bạn cần mã gỡ lỗi, hãy đặt nó vào bên trong D();. Và chương trình của bạn không bị ô nhiễm bởi những mê cung gớm ghiếc #ifdef.


6
@MatthieuM. Trên thực tế, tôi nghĩ rằng phiên bản gốc là tốt. Dấu chấm phẩy sẽ được hiểu là một câu lệnh trống. Tuy nhiên, việc quên dấu chấm phẩy có thể gây nguy hiểm.
Casey Kuball

31

#ifdef chỉ cần kiểm tra xem mã thông báo được xác định, đã cho

#define FOO 0

sau đó

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

20

Chúng tôi đã gặp phải vấn đề tương tự này trên nhiều tệp và luôn có vấn đề là mọi người quên bao gồm tệp "cờ tính năng" (Với cơ sở mã> 41.000 tệp, việc này rất dễ thực hiện).

Nếu bạn có feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Nhưng sau đó Bạn quên đưa tệp tiêu đề vào file.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

Sau đó, bạn gặp sự cố, trình biên dịch diễn giải COOL_FEATURE không được xác định là "false" trong trường hợp này và không bao gồm mã. Có gcc không hỗ trợ cờ gây ra lỗi cho các macro không xác định ... nhưng hầu hết mã của bên thứ 3 hoặc xác định hoặc không xác định các tính năng, vì vậy điều này sẽ không di động được.

Chúng tôi đã áp dụng một cách khắc phục di động cho trường hợp này cũng như kiểm tra trạng thái của tính năng: macro chức năng.

nếu bạn đã thay đổi tính năng trên.h thành:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

Nhưng sau đó, bạn lại quên đưa tệp tiêu đề vào file.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

Bộ tiền xử lý sẽ bị lỗi vì sử dụng macro chức năng không xác định.


17

Đối với mục đích thực hiện biên dịch có điều kiện, #if và #ifdef gần như giống nhau, nhưng không hoàn toàn giống nhau. Nếu quá trình biên dịch có điều kiện của bạn phụ thuộc vào hai ký hiệu thì #ifdef cũng sẽ không hoạt động. Ví dụ: giả sử bạn có hai ký hiệu biên dịch có điều kiện, PRO_VERSION và TRIAL_VERSION, bạn có thể có một cái gì đó như sau:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

Việc sử dụng #ifdef ở trên trở nên phức tạp hơn nhiều, đặc biệt là làm cho phần #else hoạt động.

Tôi làm việc trên mã sử dụng rộng rãi biên dịch có điều kiện và chúng tôi có hỗn hợp #if & #ifdef. Chúng tôi có xu hướng sử dụng # ifdef / # ifndef cho trường hợp đơn giản và #if bất cứ khi nào hai hoặc nhiều ký hiệu đang được đánh giá.


1
trong #if definednhững gì là definednó là một từ khóa hay?
nmxprime

15

Tôi nghĩ đó hoàn toàn là một câu hỏi về phong cách. Không thực sự có một lợi thế rõ ràng so với khác.

Tính nhất quán quan trọng hơn một trong hai lựa chọn cụ thể, vì vậy tôi khuyên bạn nên tập hợp với nhóm của mình và chọn một phong cách, và kiên định với nó.


8

Bản thân tôi thích:

#if defined(DEBUG_ENABLED)

Vì nó giúp dễ dàng tạo mã tìm kiếm điều kiện ngược lại dễ phát hiện hơn nhiều:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)

8
Cá nhân tôi nghĩ rằng nó dễ dàng hơn để bỏ lỡ dấu chấm than nhỏ đó!
Jon Cage

6
Với cú pháp tô sáng? :) Trong tô sáng cú pháp, chữ "n" trong "ifndef" khó phát hiện hơn nhiều vì nó đều cùng màu.
Jim Buck

Được rồi, ý tôi là #ifndef dễ phát hiện hơn so với #if! Được định nghĩa khi bạn so sánh với #if được định nghĩa .. nhưng với tất cả #if được định nghĩa / # nếu! Được định nghĩa so với # ifdef / # ifndef, cả hai đều có thể đọc được!
Jon Cage

@JonCage Tôi biết đã vài năm kể từ khi nhận xét này nhưng tôi muốn chỉ ra rằng bạn có thể viết nó #if ! definedđể làm cho nó !nổi bật hơn và khó bỏ lỡ.
Pharap

@Pharap - Đó chắc chắn trông giống như một sự cải tiến :)
Jon Cage

7

Đó là một vấn đề của phong cách. Nhưng tôi đề xuất một cách ngắn gọn hơn để làm điều này:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

Bạn làm điều này một lần, sau đó luôn sử dụng debug_print () để in hoặc không làm gì cả. (Có, điều này sẽ được biên dịch trong cả hai trường hợp.) Bằng cách này, mã của bạn sẽ không bị cắt xén với các lệnh tiền xử lý.

Nếu bạn nhận được cảnh báo "biểu thức không có hiệu lực" và muốn loại bỏ nó, đây là một giải pháp thay thế:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);

3
Có lẽ sau này macro in không phải là ví dụ tốt nhất - chúng tôi thực sự làm điều này trong cơ sở mã của chúng tôi để có mã gỡ lỗi chuẩn hơn của chúng tôi. Chúng tôi sử dụng các bit #if / #ifdefined cho các khu vực mà bạn có thể muốn bật tính năng gỡ lỗi bổ sung ..
Jon Cage

5

#ifcung cấp cho bạn tùy chọn đặt nó thành 0 để tắt chức năng, trong khi vẫn phát hiện thấy nút chuyển ở đó.
Cá nhân tôi luôn #define DEBUG 1để tôi có thể nắm bắt nó bằng #if hoặc #ifdef


1
Việc này không thành công, vì #define DEBUG = 0 bây giờ sẽ không chạy #if mà sẽ chạy
#ifdef

1
Đó là vấn đề, tôi có thể xóa hoàn toàn DEBUG hoặc chỉ cần đặt nó thành 0 để tắt nó.
Martin Beckett

nó nên được #define DEBUG 1. Không#define DEBUG=1
Keshava GN

4

#if và #define MY_MACRO (0)

Sử dụng #if có nghĩa là bạn đã tạo macro "xác định", tức là một thứ gì đó sẽ được tìm kiếm trong mã sẽ được thay thế bằng "(0)". Đây là "địa ngục vĩ mô" mà tôi ghét thấy trong C ++, vì nó gây ô nhiễm mã với các sửa đổi mã tiềm năng.

Ví dụ:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

đưa ra lỗi sau trên g ++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

Chỉ một lỗi.

Điều đó có nghĩa là macro của bạn đã tương tác thành công với mã C ++ của bạn: Lệnh gọi hàm đã thành công. Trong trường hợp đơn giản này, nó là thú vị. Nhưng kinh nghiệm của riêng tôi với các macro chơi thầm mã của tôi không phải là niềm vui và đầy đủ, vì vậy ...

#ifdef và #define MY_MACRO

Sử dụng #ifdef có nghĩa là bạn "xác định" một cái gì đó. Không phải bạn cung cấp cho nó một giá trị. Nó vẫn đang gây ô nhiễm, nhưng ít nhất, nó sẽ được "thay thế bằng không", và không bị mã C ++ coi là câu lệnh mã trễ. Đoạn mã tương tự ở trên, với một định nghĩa đơn giản, nó:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

Đưa ra các cảnh báo sau:

main.cpp||In function int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

Vì thế...

Phần kết luận

Tôi muốn sống mà không có macro trong mã của mình, nhưng vì nhiều lý do (xác định trình bảo vệ tiêu đề hoặc macro gỡ lỗi), tôi không thể.

Nhưng ít nhất, tôi muốn làm cho chúng ít tương tác nhất có thể với mã C ++ hợp pháp của tôi. Có nghĩa là sử dụng #define mà không có giá trị, sử dụng #ifdef và #ifndef (hoặc thậm chí #if được định nghĩa theo đề xuất của Jim Buck) và hơn hết, đặt tên cho họ quá dài và xa lạ sẽ không ai có suy nghĩ đúng đắn của họ sẽ sử dụng nó "tình cờ", và nó sẽ không ảnh hưởng đến mã C ++ hợp pháp.

Đoạn tái bút

Bây giờ, khi tôi đang đọc lại bài đăng của mình, tôi tự hỏi liệu tôi có nên cố gắng tìm một số giá trị không bao giờ chính xác trong C ++ để thêm vào định nghĩa của mình hay không. Cái gì đó như

#define MY_MACRO @@@@@@@@@@@@@@@@@@

có thể được sử dụng với #ifdef và #ifndef, nhưng không cho phép biên dịch mã nếu được sử dụng bên trong một hàm ... Tôi đã thử điều này thành công trên g ++ và nó xuất hiện lỗi:

main.cpp|410|error: stray ‘@’ in program|

Hấp dẫn. :-)


Tôi đồng ý rằng macro có thể nguy hiểm, nhưng ví dụ đầu tiên đó sẽ khá rõ ràng để gỡ lỗi và tất nhiên nó chỉ đưa ra một lỗi. Tại sao bạn sẽ mong đợi nhiều hơn? Tôi đã nhìn thấy lỗi nastier nhiều như là một kết quả của các macro ...
Jon Cage

Đúng là sự khác biệt giữa giải pháp này và giải pháp khác gần như là nhỏ. Nhưng trong trường hợp này, khi chúng ta đang nói về hai phong cách mã hóa cạnh tranh, thì ngay cả những thứ tầm thường cũng không thể bỏ qua, bởi vì sau đó, tất cả những gì còn lại là sở thích cá nhân (và tại thời điểm đó, tôi tin rằng nó không nên được bình thường hóa )
paercebal

4

Đó hoàn toàn không phải là vấn đề về phong cách. Ngoài ra câu hỏi không may là sai. Bạn không thể so sánh các chỉ thị tiền xử lý này theo nghĩa tốt hơn hoặc an toàn hơn.

#ifdef macro

có nghĩa là "nếu macro được xác định" hoặc "nếu macro tồn tại". Giá trị của macro không quan trọng ở đây. Nó có thể là bất cứ điều gì.

#if macro

nếu luôn so sánh với một giá trị. Trong ví dụ trên, nó là so sánh ngầm tiêu chuẩn:

#if macro !=0

ví dụ cho việc sử dụng #if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

bây giờ bạn có thể đưa định nghĩa của CFLAG_EDITION vào mã của mình

#define CFLAG_EDITION 1 

hoặc bạn có thể đặt macro làm cờ trình biên dịch. Cũng xem tại đây .


2

Điều đầu tiên có vẻ rõ ràng hơn đối với tôi. Nó có vẻ tự nhiên hơn khiến nó trở thành một lá cờ so với được xác định / không được xác định.


2

Cả hai đều hoàn toàn tương đương. Trong cách sử dụng thành ngữ, #ifdef được sử dụng chỉ để kiểm tra tính xác định (và những gì tôi muốn sử dụng trong ví dụ của bạn), trong khi #if được sử dụng trong các biểu thức phức tạp hơn, chẳng hạn như #if đã định nghĩa (A) &&! Xác định (B).


1
OP đã không yêu cầu cái nào tốt hơn giữa "#ifdef" và "#if được định nghĩa" mà là giữa "# ifdef / # nếu được xác định" và "#if".
shank

1

OT một chút, nhưng việc bật / tắt ghi nhật ký bằng bộ xử lý trước chắc chắn là không tối ưu trong C ++. Có những công cụ ghi nhật ký tuyệt vời như log4cxx của Apache , là mã nguồn mở và không hạn chế cách bạn phân phối ứng dụng của mình. Chúng cũng cho phép bạn thay đổi cấp độ ghi nhật ký mà không cần biên dịch lại, có chi phí rất thấp nếu bạn tắt chế độ ghi nhật ký và cho bạn cơ hội tắt đăng nhập hoàn toàn trong sản xuất.


1
Tôi đồng ý, và chúng tôi thực sự làm điều đó trong mã của chúng tôi, tôi chỉ muốn một ví dụ về một cái gì đó bạn có thể sử dụng # nếu vv cho
Jon Cage

1

Tôi đã từng sử dụng #ifdef , nhưng khi chuyển sang Doxygen để làm tài liệu, tôi thấy rằng không thể ghi lại các macro được nhận xét (hoặc ít nhất, Doxygen tạo ra một cảnh báo). Điều này có nghĩa là tôi không thể ghi lại các macro chuyển đổi tính năng hiện không được bật.

Mặc dù có thể xác định các macro chỉ cho Doxygen, điều này có nghĩa là các macro trong các phần không hoạt động của mã cũng sẽ được ghi lại. Cá nhân tôi muốn hiển thị các công tắc tính năng và nếu không thì chỉ ghi lại những gì hiện được chọn. Hơn nữa, nó làm cho mã khá lộn xộn nếu có nhiều macro chỉ được xác định khi Doxygen xử lý tệp.

Do đó, trong trường hợp này, tốt hơn là luôn xác định các macro và sử dụng #if.


0

Tôi đã luôn sử dụng #ifdef và cờ trình biên dịch để xác định nó ...


Bất kỳ lý do cụ thể nào (vì tò mò)?
Jon Cage

2
Thành thật mà nói, tôi chưa bao giờ nghĩ về nó - chỉ là cách nó được thực hiện ở những nơi tôi đã làm việc. Nó cung cấp cho các lợi thế mà thay vì làm một sự thay đổi mã cho một sản xuất xây dựng tất cả các bạn phải làm là 'make DEBUG' cho gỡ lỗi, hoặc 'make SẢN XUẤT' cho thường xuyên
tloach

0

Ngoài ra, bạn có thể khai báo một hằng số chung và sử dụng C ++ if, thay vì bộ tiền xử lý #if. Trình biên dịch sẽ tối ưu hóa các nhánh không sử dụng cho bạn và mã của bạn sẽ sạch hơn.

Đây là những gì C ++ Gotchas của Stephen C. Dewhurst nói về việc sử dụng # if's.


1
Đó là một giải pháp tệ hại, nó có các vấn đề sau: 1. Chỉ hoạt động trong các hàm, bạn không thể xóa các biến lớp không cần thiết, v.v. 2. Trình biên dịch có thể đưa ra cảnh báo về mã không thể truy cập được. 3. Mã trong if vẫn cần biên dịch, có nghĩa là bạn phải giữ tất cả các chức năng debug của bạn được xác định, vv
Don Neufeld

Đầu tiên, câu hỏi đặc biệt là về gỡ lỗi printfs, vì vậy các biến lớp không cần thiết không phải là vấn đề ở đây. Thứ hai, với khả năng của các trình biên dịch hiện đại, bạn nên sử dụng #ifdefs càng ít càng tốt. Trong hầu hết các trường hợp, bạn có thể sử dụng cấu hình bản dựng hoặc chuyên môn hóa mẫu để thay thế.
Dima

0

Có sự khác biệt trong trường hợp cách khác nhau để chỉ định định nghĩa có điều kiện cho trình điều khiển:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

đầu ra:

344c344
< #define A 
---
> #define A 1

Điều này có nghĩa là, đó -DAlà từ đồng nghĩa với -DA=1và nếu giá trị bị bỏ qua, thì nó có thể dẫn đến các vấn đề trong trường hợp #if Asử dụng.


0

Tôi thích #define DEBUG_ENABLED (0)khi bạn có thể muốn nhiều cấp độ gỡ lỗi. Ví dụ:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

Giúp việc gỡ lỗi rò rỉ bộ nhớ trở nên dễ dàng hơn mà không cần phải có tất cả các dòng nhật ký đó theo cách của bạn để gỡ lỗi những thứ khác.

Ngoài ra, #ifndefxung quanh định nghĩa giúp dễ dàng hơn để chọn một mức gỡ lỗi cụ thể trong dòng lệnh:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Nếu không phải vì điều này, tôi sẽ cho lợi thế #ifdefvì cờ trình biên dịch / make sẽ bị ghi đè bởi cờ trong tệp. Vì vậy, bạn không phải lo lắng về việc thay đổi lại tiêu đề trước khi thực hiện cam kết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.