Tại sao chỉ xác định một macro nếu nó chưa được xác định?


93

Trên toàn bộ cơ sở mã C của chúng tôi, tôi thấy mọi macro được xác định theo cách sau:

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

#ifndef BEEPTRIM_ROLL_RATE_DEGPS
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#endif

#ifndef FORCETRIMRELEASE_HOLD_TIME_MS
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#endif

#ifndef TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f
#endif

Cơ sở lý luận của việc thực hiện những kiểm tra xác định này thay vì chỉ xác định macro là gì?

#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f

Tôi không thể tìm thấy thực hành này được giải thích ở bất kỳ đâu trên web.


6
Thay đổi hằng số ở một nơi khác trong mã được đảm bảo hoạt động theo cách này. Nếu ở đâu đó người khác xác định một trong những macro đó, chúng sẽ không bị bộ xử lý tiền ghi đè khi nó phân tích cú pháp tệp này.
Enzo Ferber

8
Đó là một ví dụ về nguyên tắc thiết kế WET.
stark

Đã đăng một câu trả lời kèm theo ví dụ, hãy thử biên dịch nó.
Enzo Ferber

Câu trả lời:


141

Điều này cho phép bạn ghi đè các macro khi bạn đang biên dịch:

gcc -DMACRONAME=value

Các định nghĩa trong tệp tiêu đề được sử dụng làm mặc định.


51

Như tôi đã nói trong phần bình luận, hãy tưởng tượng tình huống này:

foo.h

#define FOO  4

defs.h

#ifndef FOO
#define FOO 6
#endif

#ifndef BAR
#define BAR 4
#endif

bar.c

#include "foo.h"
#include "defs.h"

#include <stdio.h>

int main(void)
{
    printf("%d%d", FOO, BAR);
    return 0;
}

Sẽ in 44.

Tuy nhiên, nếu điều kiện ifndefkhông có ở đó, kết quả sẽ là các cảnh báo biên dịch về định nghĩa lại MACRO và nó sẽ được in 64.

$ gcc -o bar bar.c
In file included from bar.c:2:0:
defs.h:1:0: warning: "FOO" redefined [enabled by default]
 #define FOO 6
 ^
In file included from bar.c:1:0:
foo.h:1:0: note: this is the location of the previous definition
 #define FOO 4
 ^

1
Đây là trình biên dịch cụ thể. Việc xác định lại một macro giống đối tượng là bất hợp pháp trừ khi định nghĩa lại là "giống nhau" (có một thông số kỹ thuật hơn cho điều đó, nhưng nó không quan trọng ở đây). Mã không hợp lệ yêu cầu chẩn đoán và sau khi đưa ra chẩn đoán (ở đây là cảnh báo), trình biên dịch có thể tự do làm bất cứ điều gì, bao gồm cả biên dịch mã với kết quả cụ thể cho việc triển khai.
Pete Becker

7
Nếu bạn có các lỗi xung đột cho cùng một macro, bạn có muốn nhận được cảnh báo trong hầu hết các trường hợp không? Thay vì âm thầm sử dụng định nghĩa đầu tiên (vì định nghĩa thứ hai sử dụng một ifdefđể tránh xác định lại).
Peter Cordes

@PeterCordes Hầu hết các lần, định nghĩa dưới #infdefs được sử dụng làm giá trị "dự phòng" hoặc "mặc định". Về cơ bản, "nếu người dùng định cấu hình nó, tốt. Nếu không, hãy sử dụng giá trị mặc định."
Angew không còn tự hào về SO

@Angew: Ok, vì vậy nếu bạn có một số #definestrong tiêu đề thư viện là một phần của ABI của thư viện, bạn không nên bọc chúng vào #ifndef. (Hoặc tốt hơn, sử dụng một enum). Tôi chỉ muốn làm rõ rằng #ifndefchỉ phù hợp khi có một định nghĩa tùy chỉnh cho một thứ gì đó trong một đơn vị biên dịch chứ không phải đơn vị khác là ổn. Nếu a.cbao gồm các tiêu đề theo thứ tự khác với b.c, chúng có thể nhận được các định nghĩa khác nhau max(a,b)và một trong các định nghĩa đó có thể ngắt với max(i++, x), nhưng định nghĩa kia có thể sử dụng các dấu tạm thời trong biểu thức câu lệnh GNU. Vẫn còn khó hiểu ít nhất!
Peter Cordes

@PeterCordes Những gì tôi muốn làm trong trường hợp đó là#ifdef FOO #error FOO already defined! #endif #define FOO x
Cole Johnson

17

Tôi không biết bối cảnh nhưng điều này có thể được sử dụng để cung cấp cho người dùng khả năng ghi đè các giá trị được đặt bởi các định nghĩa macro đó. Nếu người dùng xác định rõ ràng một giá trị khác cho bất kỳ macro nào, nó sẽ được sử dụng thay vì các giá trị được sử dụng ở đây.

Ví dụ: trong g ++, bạn có thể sử dụng -Dcờ trong quá trình biên dịch để chuyển một giá trị cho một macro.


14

Điều này được thực hiện để người dùng tệp tiêu đề có thể ghi đè các định nghĩa từ mã của họ hoặc từ cờ -D của trình biên dịch.


7

Bất kỳ dự án C nào nằm trên nhiều tệp nguồn. Khi làm việc trên một tệp nguồn duy nhất, việc kiểm tra dường như (và thực sự) không có ích lợi gì, nhưng khi làm việc trên một dự án C lớn, bạn nên kiểm tra các định nghĩa hiện có trước khi xác định một hằng số. Ý tưởng rất đơn giản: bạn cần hằng số trong tệp nguồn cụ thể đó, nhưng nó có thể đã được định nghĩa trong tệp khác.


2

Bạn có thể nghĩ về một khuôn khổ / thư viện cung cấp cho người dùng một giá trị đặt trước mặc định cho phép người dùng biên dịch và làm việc trên đó. Những định nghĩa đó được rải trong các tệp khác nhau và người dùng cuối cùng được khuyên nên đưa vào tệp config.h nơi anh ta có thể định cấu hình các giá trị của nó. Nếu người dùng quên một số định nghĩa, hệ thống có thể tiếp tục hoạt động do cài đặt trước.


1

Sử dụng

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

cho phép người dùng xác định giá trị của macro bằng cách sử dụng đối số dòng lệnh (trong gcc / clang / VS) -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f.

Có một lý do quan trọng khác. Đó là một lỗi khi xác định lại một macro bộ tiền xử lý theo cách khác. Xem câu trả lời này cho một câu hỏi SO khác . Nếu không có #ifndefdấu kiểm, trình biên dịch sẽ tạo ra lỗi nếu -DBEEPTRIM_PITCH_RATE_DEGPS=0.3fđược sử dụng làm đối số dòng lệnh trong lệnh gọi trình biên dịch.

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.