Tại sao #pragma một khi không được tự động giả định?


81

Mục đích của việc yêu cầu trình biên dịch cụ thể chỉ bao gồm tệp một lần là gì? Nó sẽ không có ý nghĩa theo mặc định? Thậm chí có bất kỳ lý do gì để bao gồm một tệp duy nhất nhiều lần? Tại sao không chỉ giả sử nó? Nó có liên quan đến phần cứng cụ thể không?


24
Thậm chí có bất kỳ lý do gì để bao gồm một tệp duy nhất nhiều lần? => Có thể. Một tệp có thể có biên dịch có điều kiện #ifdefstrong đó. Vì vậy, bạn có thể nói #define MODE_ONE 1và sau đó #include "has-modes.h", và sau đó #undef MODE_ONEvới #define MODE_TWO 1#include "has-modes.h"một lần nữa. Bộ tiền xử lý không có khả năng hiểu biết về những thứ này, và đôi khi chúng có thể có ý nghĩa.
HostileFork nói không tin tưởng SE,

66
Nó sẽ có ý nghĩa như mặc định. Chỉ cần không phải là người họ chọn khi lập trình C vẫn cưỡi ngựa, súng tiến hành và có 16KB bộ nhớ
Hans passant

11
Bạn có thể bao gồm <assert.h>nhiều lần, với các định nghĩa khác nhau NDEBUG, trong cùng một tệp nguồn.
Pete Becker

3
Đối với #pragma oncechính nó, có những môi trường phần cứng (thường với các ổ đĩa được nối mạng và có thể có nhiều đường dẫn đến cùng một tiêu đề) nơi nó sẽ không hoạt động bình thường.
Pete Becker

12
Nếu bạn đã #pragma oncegiả định, cách khắc phục sự mặc định đó là gì? #pragma many? Có bao nhiêu trình biên dịch đã thực hiện bất cứ điều gì như vậy?
Jonathan Leffler

Câu trả lời:


84

Có nhiều câu hỏi liên quan ở đây:

  • Tại sao #pragma oncekhông được tự động thực thi?
    Bởi vì có những trường hợp bạn muốn bao gồm các tệp nhiều hơn một lần.

  • Tại sao bạn muốn bao gồm một tệp nhiều lần?
    Một số lý do đã được đưa ra trong các câu trả lời khác (Boost.Preprocessor, X-Macros, bao gồm các tệp dữ liệu). Tôi muốn thêm một ví dụ cụ thể về "tránh trùng lặp mã": OpenFOAM khuyến khích một phong cách mà việc nhập #includecác bit và mảnh trong các hàm là một khái niệm phổ biến. Xem ví dụ thảo luận này .

  • Được, nhưng tại sao nó không phải là mặc định với tùy chọn không tham gia?
    Bởi vì nó không thực sự được quy định bởi tiêu chuẩn. #pragmatheo định nghĩa là phần mở rộng dành riêng cho triển khai.

  • Tại sao vẫn #pragma oncechưa trở thành một tính năng được tiêu chuẩn hóa (vì nó được hỗ trợ rộng rãi)?
    Bởi vì việc xác định "cùng một tệp" theo cách bất khả tri nền tảng thực sự khó một cách đáng ngạc nhiên. Xem câu trả lời này để biết thêm thông tin .



4
Đặc biệt, hãy xem ví dụ này để biết trường hợp pragma oncekhông thành công nhưng bao gồm các vệ sĩ sẽ hoạt động. Việc xác định tệp theo vị trí cũng không hoạt động vì đôi khi cùng một tệp xảy ra nhiều lần trong một dự án (ví dụ: bạn có 2 mô-đun con đều bao gồm thư viện chỉ dành cho tiêu đề trong tiêu đề của chúng và hãy kiểm tra bản sao của chính chúng)
MM

6
Không phải tất cả các pragmas đều là phần mở rộng dành riêng cho việc triển khai. Vd: #pragma STDCgia đình . Nhưng tất cả chúng đều kiểm soát hành vi do triển khai xác định.
Ruslan

3
@ user4581301 Câu trả lời này quá phóng đại vấn đề với pragma một lần và không xem xét các vấn đề do bao gồm bảo vệ. Trong cả hai trường hợp, một số kỷ luật là cần thiết. Với các bảo vệ bao gồm, người ta phải đảm bảo sử dụng một tên sẽ không được sử dụng trong một tệp khác (sẽ xảy ra sau khi bản sao tệp sửa đổi). Với pragma một lần, người ta phải quyết định đâu là vị trí phù hợp duy nhất cho tệp của nó, đó là một điều tốt.
Oliv

3
@Mehrdad: Bạn có thực sự đề xuất rằng các trình biên dịch ghi vào các tệp nguồn không !? Nếu một trình biên dịch nhìn thấy #ifndef XX, nó phải không thể biết liệu có bất kỳ thứ gì theo sau tương ứng hay không #endifcho đến khi nó đọc toàn bộ tệp. Một trình biên dịch theo dõi xem lớp ngoài cùng có #ifndefbao quanh toàn bộ tệp hay không và ghi chú macro mà nó kiểm tra có thể tránh quét lại tệp hay không, nhưng một chỉ thị để nói rằng không có gì quan trọng sau thời điểm hiện tại sẽ có vẻ đẹp hơn việc dựa vào trình biên dịch nhớ những điều như vậy.
supercat

38

Bạn có thể sử dụng ở #include bất kỳ đâu trong tệp, không chỉ ở phạm vi toàn cục - như bên trong một hàm (và nhiều lần nếu cần). Chắc chắn, phong cách xấu và không tốt, nhưng có thể và đôi khi hợp lý (tùy thuộc vào tệp bạn đưa vào). Nếu #includechỉ có một lần thì điều đó sẽ không hiệu quả. #includeRốt cuộc chỉ thay thế văn bản ngu ngốc (cut'n'paste) và không phải mọi thứ bạn đưa vào đều phải là tệp tiêu đề. Bạn có thể - ví dụ - #includemột tệp chứa dữ liệu được tạo tự động chứa dữ liệu thô để khởi tạo a std::vector. Giống

std::vector<int> data = {
#include "my_generated_data.txt"
}

Và có "my_generated_data.txt" là thứ do hệ thống xây dựng tạo ra trong quá trình biên dịch.

Hoặc có thể tôi lười biếng / ngớ ngẩn / ngu ngốc và đưa nó vào một tệp ( ví dụ rất giả tạo):

const noexcept;

và sau đó tôi làm

class foo {
    void f1()
    #include "stupid.file"
    int f2(int)
    #include "stupid.file"
};

Một ví dụ khác, ít có nội dung hơn một chút, sẽ là một tệp nguồn trong đó nhiều hàm cần sử dụng một lượng lớn các loại trong một không gian tên, nhưng bạn không muốn chỉ nói using namespace foo;toàn cục vì điều đó sẽ phân biệt không gian tên chung với nhiều thứ khác bạn không muốn. Vì vậy, bạn tạo một tệp "foo" chứa

using std::vector;
using std::array;
using std::rotate;
... You get the idea ...

Và sau đó bạn thực hiện việc này trong tệp nguồn của mình

void f1() {
#include "foo" // needs "stuff"
}

void f2() {
    // Doesn't need "stuff"
}

void f3() {
#include "foo" // also needs "stuff"
}

Lưu ý: Tôi không ủng hộ việc làm như thế này. Nhưng nó có thể và được thực hiện trong một số cơ sở mã và tôi không hiểu tại sao nó không được phép. Nó không có mục đích sử dụng của nó.

Cũng có thể là tệp bạn đưa vào hoạt động khác nhau tùy thuộc vào giá trị của ( #definecác) macro nhất định . Vì vậy, bạn có thể muốn bao gồm tệp ở nhiều vị trí, sau khi lần đầu tiên đã thay đổi một số giá trị, vì vậy bạn sẽ có hành vi khác nhau trong các phần khác nhau của tệp nguồn của mình.


1
Điều này sẽ vẫn hoạt động nếu tất cả các tiêu đề đều là pragma một lần. Miễn là bạn không bao gồm dữ liệu đã tạo nhiều lần.
PSkocik

2
@PSkocik Nhưng có lẽ tôi cần thêm nó nhiều lần. Tại sao tôi không thể?
Jesper Juhl

2
@JesperJuhl Đó là vấn đề. Bạn sẽ không cần phải bao gồm nó nhiều lần. Bạn hiện có tùy chọn, nhưng lựa chọn thay thế không tệ hơn nhiều, nếu có.
Johnny Cache

9
@PSkocik Nếu tôi thay đổi giá trị của #defines trước mỗi bao gồm thay đổi hành vi của tệp được bao gồm thì rất có thể tôi cần phải bao gồm nhiều lần để có được các hành vi khác nhau đó trong các phần khác nhau của tệp nguồn của mình.
Jesper Juhl

27

Bao gồm nhiều lần có thể sử dụng được, ví dụ, với kỹ thuật X-macro :

data.inc:

X(ONE)
X(TWO)
X(THREE)

use_data_inc_twice.c

enum data_e { 
#define X(V) V,
   #include "data.inc"
#undef X
};
char const* data_e__strings[]={
#define X(V) [V]=#V,
   #include "data.inc"
#undef X
};

Tôi không biết về bất kỳ cách sử dụng nào khác.


Điều đó nghe có vẻ quá phức tạp. Có lý do gì để không đưa những định nghĩa đó vào tệp ngay từ đầu?
Johnny Cache

2
@JohnnyCache: Ví dụ là một phiên bản đơn giản hóa về cách hoạt động của X-macro. Vui lòng đọc liên kết; chúng cực kỳ hữu ích trong một số trường hợp để thực hiện các thao tác phức tạp với dữ liệu dạng bảng. Trong bất kỳ cách sử dụng quan trọng nào của X-macro, sẽ không có cách nào bạn chỉ có thể "đưa các định nghĩa đó vào tệp".
Nicol Bolas

2
@Johnny - vâng - một lý do chính đáng là đảm bảo tính nhất quán (khó làm bằng tay khi bạn chỉ có vài chục phần tử, đừng bận tâm đến hàng trăm).
Toby Speight

@TobySpeight Heh, tôi cho rằng tôi có thể dành một dòng mã duy nhất để tránh phải viết hàng nghìn mã ở nơi khác. Có ý nghĩa.
Johnny Cache

1
Để tránh trùng lặp. Đặc biệt nếu tệp lớn. Phải thừa nhận rằng bạn chỉ có thể sử dụng một macro lớn chứa danh sách macro X nhưng vì các dự án có thể đang sử dụng điều này, #pragma oncehành vi ủy thác sẽ là một thay đổi đột phá.
PSkocik

21

Dường như bạn đang hoạt động theo giả định rằng mục đích của tính năng "#include" thậm chí còn tồn tại trong ngôn ngữ là cung cấp hỗ trợ cho việc phân rã các chương trình thành nhiều đơn vị biên dịch. Điều đó là không chính xác.

Nó có thể thực hiện vai trò đó, nhưng đó không phải là mục đích của nó. C ban đầu được phát triển như một ngôn ngữ cấp cao hơn một chút so với PDP-11 Macro-11 Assembly để hoàn thiện lại Unix. Nó có một bộ xử lý tiền macro vì đó là một tính năng của Macro-11. Nó có khả năng bao gồm văn bản các macro từ một tệp khác vì đó là một tính năng của Macro-11 mà Unix hiện có mà họ đang chuyển sang trình biên dịch C mới của họ đã sử dụng.

Bây giờ, hóa ra "#include" hữu ích để tách mã thành các đơn vị biên dịch, vì (có thể cho là) ​​một chút hack. Tuy nhiên, thực tế là hack này tồn tại có nghĩa là nó đã trở thành Cách được thực hiện ở C. Thực tế là một cách tồn tại có nghĩa là không cần tạo ra phương pháp mới nào để cung cấp chức năng này, vì vậy không có gì an toàn hơn (ví dụ: không dễ bị tổn thương bởi nhiều -bao gồm) đã từng được tạo. Vì nó đã có trong C, nó đã được sao chép vào C ++ cùng với hầu hết các cú pháp và thành ngữ còn lại của C.

Có một đề xuất về việc cung cấp cho C ++ một hệ thống mô-đun thích hợp để bản hack tiền xử lý 45 năm tuổi này cuối cùng có thể được phân phát. Tôi không biết điều này sắp xảy ra như thế nào. Tôi đã nghe nói về việc nó đang hoạt động trong hơn một thập kỷ.


5
Như thường lệ, để hiểu C và C ++, bạn cần hiểu lịch sử của chúng.
Jack Aidley

Nó là hợp lý để mong đợi rằng các mô-đun sẽ hạ cánh vào tháng Hai.
Davis Herring

7
@DavisHerring - Có, nhưng tháng 2 nào?
TED

10

Không, điều này sẽ cản trở đáng kể các tùy chọn có sẵn cho người viết thư viện chẳng hạn. Ví dụ, Boost.Preprocessor cho phép một người sử dụng các vòng lặp tiền xử lý và cách duy nhất để đạt được những điều đó là sử dụng nhiều phần bao gồm của cùng một tệp.

Và Boost.Preprocessor là một khối xây dựng cho nhiều thư viện rất hữu ích.


1
Nó sẽ không cản trở bất kỳ điều đó. OP hỏi về một hành vi mặc định , không phải là một hành vi không thể thay đổi. Sẽ hoàn toàn hợp lý nếu thay đổi mặc định và thay vào đó cung cấp một cờ tiền xử lý #pragma reentranthoặc một cái gì đó dọc theo những dòng này. Nhận thức muộn là 20/20.
Konrad Rudolph

Nó sẽ cản trở nó theo nghĩa buộc mọi người phải cập nhật thư viện và phần phụ thuộc của họ, @KonradRudolph. Không phải lúc nào cũng là vấn đề, nhưng nó có thể gây ra sự cố với một số chương trình cũ. Lý tưởng nhất, cũng sẽ có một công tắc dòng lệnh để chỉ định xem mặc định là oncehay reentrant, để giảm thiểu vấn đề này hoặc các vấn đề tiềm ẩn khác.
Justin Time - Phục hồi Monica

1
@JustinTime Cũng như nhận xét của tôi nói rằng nó rõ ràng không phải là một thay đổi tương thích ngược (và do đó khả thi). Tuy nhiên, câu hỏi đặt ra là tại sao ban đầu nó được thiết kế theo cách đó chứ không phải tại sao nó không được thay đổi. Và câu trả lời cho điều đó, rõ ràng là, thiết kế ban đầu là một sai lầm lớn với hậu quả sâu rộng.
Konrad Rudolph

8

Trong phần sụn cho sản phẩm mà tôi chủ yếu làm việc, chúng ta cần có khả năng chỉ định nơi các hàm và biến toàn cục / tĩnh nên được cấp phát trong bộ nhớ. Xử lý thời gian thực cần sống trong bộ nhớ L1 trên chip để bộ xử lý có thể truy cập trực tiếp, nhanh chóng. Xử lý ít quan trọng hơn có thể đi trong bộ nhớ L2 trên chip. Và bất cứ thứ gì không cần xử lý đặc biệt kịp thời đều có thể nằm trong bộ nhớ DDR bên ngoài và chuyển qua bộ nhớ đệm, vì nó chậm hơn một chút cũng không thành vấn đề.

#Pragma để phân bổ nơi mọi thứ diễn ra là một đường dài, không tầm thường. Sẽ rất dễ dàng để làm sai. Hiệu quả của việc làm sai sẽ là mã / dữ liệu sẽ được đưa vào bộ nhớ mặc định (DDR) một cách âm thầm và ảnh hưởng của điều đó có thể là điều khiển vòng kín ngừng hoạt động mà không có lý do gì dễ thấy.

Vì vậy, tôi sử dụng các tệp bao gồm, chỉ chứa pragma đó. Mã của tôi bây giờ trông như thế này.

Tập tin tiêu đề...

#ifndef HEADERFILE_H
#define HEADERFILE_H

#include "set_fast_storage.h"

/* Declare variables */

#include "set_slow_storage.h"

/* Declare functions for initialisation on startup */

#include "set_fast_storage.h"

/* Declare functions for real-time processing */

#include "set_storage_default.h"

#endif

Và nguồn ...

#include "headerfile.h"

#include "set_fast_storage.h"

/* Define variables */

#include "set_slow_storage.h"

/* Define functions for initialisation on startup */

#include "set_fast_storage.h"

/* Define functions for real-time processing */

Bạn sẽ nhận thấy nhiều bao gồm của cùng một tệp ở đó, thậm chí chỉ trong tiêu đề. Nếu tôi nhập sai nội dung nào đó ngay bây giờ, trình biên dịch sẽ cho tôi biết rằng nó không thể tìm thấy tệp bao gồm "set_fat_storage.h" và tôi có thể dễ dàng sửa nó.

Vì vậy, để trả lời cho câu hỏi của bạn, đây là một ví dụ thực tế, thực tế về nơi yêu cầu bao gồm nhiều.


3
Tôi muốn nói rằng trường hợp sử dụng của bạn là một ví dụ thúc đẩy cho _Pragmachỉ thị. Các pragmas tương tự hiện có thể được mở rộng từ các macro thông thường. Vì vậy, không cần phải bao gồm nhiều hơn một lần.
StoryTeller - Unslander Monica
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.