2009-11-23 29 views
19

Câu hỏi này có vẻ khá cơ bản, nhưng đến từ nền tảng kỹ thuật (phi khoa học máy tính), tôi không chắc chắn về những đoạn mã '#' ở trong một số mã C++.Tại sao một người nên bận tâm với các chỉ thị tiền xử lý?

Tìm kiếm nhanh đã dẫn tôi đến trang hướng dẫn ngắn gọn, được giải thích rõ ràng trên trang hướng dẫn tiền xử lý trang cplusplus.

Nhưng tại sao lại bận tâm với khái niệm chỉ thị tiền xử lý? Là nó không thể viết mã tương đương có thể gán giá trị cho hằng số, xác định chương trình con/chức năng/macro và xử lý lỗi?

Tôi đoán cuối cùng tôi muốn biết khi nào thực hành tốt để sử dụng các chỉ thị tiền xử lý như vậy và khi nào thì không.

+11

Điều này làm tôi hài lòng. :) – epochwolf

+0

Nhiều điểm tốt trên nhiều câu trả lời ... nó sẽ đưa tôi một thời gian để tiêu hóa tất cả những điều này! – Zaid

+1

Tôi có nên cộng đồng wiki câu hỏi này không? – Zaid

Trả lời

25

Bạn sử dụng chỉ thị tiền xử lý khi bạn cần thực hiện điều gì đó ngoài phạm vi của ứng dụng thực tế. Ví dụ, bạn sẽ thấy tiền xử lý được thực hiện để bao gồm hoặc không bao gồm mã dựa trên kiến ​​trúc mà tệp thực thi đang được xây dựng. Ví dụ:

#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers 
#include <windows.h> 
#else 
#include <unistd.h> 
#endif 

Chỉ thị tiền xử lý cũng được sử dụng để bảo vệ để các lớp/chức năng, v.v. không được xác định nhiều lần.

#ifndef MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_ 
#define MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_ 
    class MyClass { 
    .... 
    }; 
#endif 

Một cách khác là nhúng phiên bản bên trong mã và thư viện.

Trong Makefile bạn có một cái gì đó dọc theo dòng:

-D MY_APP_VERSION=4.5.1 

Trong khi ở các mã bạn có

cout << "My application name version: " << MY_APP_VERSION << endl; 
+0

Điều này đặc biệt quan trọng trong trường hợp có các chức năng tồn tại trên một nền tảng nhưng không có chức năng khác - như trình biên dịch nội tại, bao gồm mã hóa SSE trên mã x86 và VMX trên PPC, v.v. Trong trường hợp này, không có cách thay thế khả thi nào để sử dụng #ifdef, vì các mẫu sẽ cố gắng biên dịch mã bằng cách sử dụng các hàm không tồn tại, ngay cả khi chỉ sau đó mới loại bỏ chúng. – Crashworks

+0

Một nitpick: WIN32 được xác định bởi vì đó là cài đặt dự án mặc định. Trình biên dịch tự xác định _WIN32 (tôi tin rằng MinGW hoặc các trình biên dịch dựa trên windows khác định nghĩa điều này), và tùy chọn _WIN64, để mô tả nền tảng này. Nó cũng định nghĩa _MSC_VER là phiên bản trình biên dịch. – Tom

+0

Hah .. xin lỗi ... một nitpick khác. '__MY_HEADER_H__' là xấu, vì' __ [A-Z] 'được đặt trước về mặt kỹ thuật để triển khai. Hầu hết các trình biên dịch xác định các cài đặt cấu hình * nhiều *, vì vậy nó không có khả năng cuối cùng chạy vào một lỗi liên quan đến macro lạ từ này. – Tom

7

Vì chỉ thị tiền xử lý được thực thi tại thời điểm xây dựng, trong khi mã bạn viết sẽ được thực hiện tại chạy thời gian. Vì vậy, các chỉ thị tiền xử lý hiệu quả cung cấp cho bạn khả năng sửa đổi mã nguồn của bạn theo lập trình.

Lưu ý rằng bộ tiền xử lý C là một cơ chế khá thô lỗ đối với loại điều này; Hệ thống mẫu của C++ cung cấp một khung công tác mạnh mẽ hơn để biên dịch mã. Các ngôn ngữ khác thậm chí còn có các tính năng siêu lập trình mạnh mẽ hơn (ví dụ, hệ thống macro của Lisp).

+3

Để nitpick một chút: chỉ thị tiền xử lý được thực thi trước thời gian biên dịch. –

+0

@Nemanja: Đúng vậy. Có lẽ tôi nên nói "thời gian thế hệ thực thi". Sự khác biệt chủ yếu là một chi tiết thực hiện của toolchain trình biên dịch, tuy nhiên. –

+1

Vâng, không, nó cần phải được * pre * xử lý trước khi trình biên dịch nhìn vào mã. –

9

Trả lời 1: mã có điều kiện phải thay đổi tùy thuộc vào loại máy tính mà nó hoạt động.

Trả lời 2: bật và tắt tiện ích mở rộng ngôn ngữ và các tính năng tương thích được thấy tại thời gian biên dịch.

Bộ tiền xử lý đến từ C, nơi có nhiều thứ bạn không thể diễn tả. Tốt mã C++ tìm thấy ít lý do để sử dụng nó hơn so với mã C đã làm, nhưng thật đáng buồn nó không hoàn toàn vô ích.

+2

Ngoài ra, vì C++ có "nội dòng", bạn không nên sử dụng các macro #define trong C++ để thử tốc độ. Xem thêm http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preprocessor_Macros –

+0

@Harold: Nội dung được đề cập trong câu trả lời này ở đâu? –

5

Nó được sử dụng thường xuyên nhất đối với hai điều đó sẽ khó khăn hơn để tổ chức mà không có nó:

  1. Include guards.
  2. Các phần mã khác nhau cho các nền tảng khác nhau.
1

Không, nó thực sự không thể có được mà không có bộ tiền xử lý trong mọi trường hợp. Một trong những macro ưa thích của tôi là

#define IFDEBUG if(DEBUG==1) 
//usage: 
IFDEBUG{ 
    printf("Dump of stuff:.,,,"); 
}else{ 
    //..do release stuff 
} 

Nếu không có macro, tôi sẽ phải (có thể rất nhiều) không gian lãng phí trong thực thi chính thức

Và cũng có thể bạn phải nhận ra, C/C++ không có bất kỳ loại loại gói require hoặc hệ thống khác như vậy. Vì vậy, không có bộ tiền xử lý, không có cách nào để ngăn chặn việc sao chép mã. (không thể bao gồm các tệp tiêu đề)

+4

Điều này sẽ để lại kiểm tra "if" trong tệp thực thi cuối cùng: bạn muốn #ifdef bảo vệ xung quanh mã chỉ DEBUG và sau đó biên dịch có hoặc không có -DDEBUG để kiểm soát xem printf có nằm trong mã chương trình hay không. –

+0

Chính xác của bạn, tôi đã đưa ra một ví dụ xấu. Nhưng tôi sẽ cầu nguyện tôi đã sử dụng một trình biên dịch đủ thẩm quyền để tối ưu hóa đi 'if (0) {..}' – Earlz

+0

Ví dụ xấu, IMO; tại sao không đơn giản là '#if định nghĩa DEBUG'? – Clifford

0

Đó là một sự thay thế tốt hơn không có gì để có được một số khả năng phản xạ ra khỏi C++.

Rất hữu ích để tạo biến và chuỗi có cùng tên.

+0

Không C++ 0x có hỗ trợ phản chiếu (ish) trong đó với vectơ và tất cả nhạc jazz đó? – Earlz

+0

Không. Và ý của bạn là gì? std :: vector <> đã được khoảng 15 năm hoặc lâu hơn. – Macke

1

Nhưng tại sao lại bận tâm với khái niệm chỉ thị tiền xử lý? Là nó không thể viết mã tương đương có thể gán giá trị cho hằng số, xác định chương trình con/chức năng/macro và xử lý lỗi?

Việc sử dụng nó rất thấp trong C++, các tính năng của ngôn ngữ được tạo để tránh các sự cố liên quan đến bộ tiền xử lý.

Tôi đoán cuối cùng tôi muốn biết khi nào thực hành tốt để sử dụng các chỉ thị tiền xử lý như vậy và khi nào thì không.

Nói chung nguồn C++, thường được xem là thực hành kém - đặc biệt khi có ý nghĩa để thực hiện điều đó bằng các tính năng ngôn ngữ khác. Nó là bắt buộc đối với một số điều (tức là các chương trình phụ thuộc vào nền tảng/xây dựng và các chương trình sinh trưởng). Nói tóm lại, thường có một sự thay thế có quy mô tốt. (chẳng hạn như hằng số xác định là enum hoặc mẫu nội tuyến thay vì macro). Nếu bạn thấy mình sử dụng một và bạn không chắc chắn, sau đó chỉ cần hỏi/tìm kiếm nếu có một cách tốt hơn để khai báo this_code_snippet trong C++, không có bộ tiền xử lý.

0

Trình tiền xử lý C thực hiện một số tác vụ, một số nhưng không phải tất cả các tác vụ có lựa chọn thay thế tốt hơn trong C++. Trường hợp C++ có một lựa chọn thay thế tốt hơn nó. Những lựa chọn thay thế bao gồm các mẫu, nội tuyến và các biến số const (một oxymoron, nhưng đó là những gì tiêu chuẩn gọi chúng) thay cho # macro xác định.

Tuy nhiên, có một vài điều bạn không muốn làm mà không có hoặc đơn giản là không thể làm mà không có; Ví dụ: #include là cần thiết và khi mã hóa cho nhiều nền tảng hoặc cấu hình, việc biên dịch có điều kiện vẫn hữu ích (mặc dù nên được sử dụng ít trong mọi trường hợp).

Tiện ích mở rộng cụ thể của trình biên dịch được kiểm soát qua #pragma có thể không tránh khỏi trong một số trường hợp.

+0

Vâng ... Tôi không thể tưởng tượng cách '# include' và' # pragma' sẽ được viết theo bất kỳ cách nào khác – Zaid

+0

Bạn cũng có thể tránh #include bằng cách sao chép tất cả nội dung vào một tệp! ;) Đó là lý do tại sao nó là một điều tốt không phải là một điều xấu. #pragma vẫn còn tệ ngay cả khi bạn phải sử dụng nó - vì các lý do về tính di động. – Clifford

+1

... hoặc bằng cách tạo cấu trúc ngôn ngữ thích hợp để xử lý giao diện cho các đơn vị biên dịch khác, thay vì dựa vào bản tiền xử lý trước cũ 40 năm này. (Đừng quên thêm '# ifdef' tất cả mọi người phải đặt tất cả mọi thứ trong tệp được bao gồm của họ để có được hành vi mong muốn thích hợp của một giao diện thực). –

6

Quá trình tiền xử lý xảy ra trước khi mã được biên dịch. Điều này thích hợp trong các trường hợp như sau

#ifdef WIN32 
#include <something.h> 
#elseif LINUX 
#include <somethingelse.h> 
#endif 

Rõ ràng bao gồm các tệp tiêu đề bạn muốn thực hiện tại thời gian biên dịch không chạy. Chúng ta không thể làm điều này với các biến.

Mặt khác.Với C++, đó là thực hành tốt và được khuyến khích để thay thế các biểu thức liên tục như ví dụ sau

#define PI 3.141592654 
with 
const double PI=3.141592654; 

Lý do là bạn xử lý đúng kiểu và xử lý dữ liệu.

Cũng

#define MAX(x,y) (x) > (y) ? (x) : (y) 

Không phải là rất tốt đẹp bởi vì bạn có thể viết

int i = 5 
int max = MAX(i++, 6); 

Các preprocessor sẽ thay thế điều đó với:

int max = (i++) > (6) ? (i++) : (6); 

Đó là rõ ràng sẽ không cung cấp cho các mục đích kết quả.

Thay vào đó, MAX phải là hàm (không phải macro). Nếu đó là một hàm, nó cũng có thể cung cấp kiểu trong tham số.

Tôi đã thấy bộ tiền xử lý được sử dụng cho tất cả các loại điều thú vị. Giống như khai báo từ khóa ngôn ngữ. Nó có thể hỗ trợ dễ đọc trong trường hợp này.

Tóm lại, sử dụng bộ tiền xử lý cho những thứ phải xảy ra ở loại biên dịch, chẳng hạn như chỉ thị có điều kiện. Tránh sử dụng nó cho các hằng số. Tránh các macro và thay vào đó sử dụng các chức năng nếu có thể.

+0

Không chắc chắn rằng tôi sẽ phải sử dụng tính tương thích đa nền tảng, nhưng tôi có được hình ảnh. – Zaid

+0

Lưu ý rằng max() đã có trong thư viện chuẩn C++, tất cả đều được tạo khuôn mẫu và các công cụ độc đáo. – ceo

2

Nói chung, chỉ thị tiền xử lý không nên được sử dụng. Đáng buồn thay, đôi khi bạn phải trong C và C++.

C ban đầu xác định ngôn ngữ theo cách mà bạn thực sự không thể làm bất kỳ điều gì nghiêm trọng với ngôn ngữ đó mà không sử dụng bộ tiền xử lý. Ngôn ngữ không có hỗ trợ xây dựng khác để tạo ra các chương trình mô-đun, hằng số, mã nội tuyến hoặc lập trình chung.

C++ loại bỏ hầu hết các vấn đề này nhưng cơ sở vẫn còn ở đó, vì vậy nó vẫn được sử dụng. (Thật thú vị, không phải là mô đun hóa mô-đun hóa. Chúng tôi vẫn bị mắc kẹt với #include),

Nếu bạn muốn so sánh với một ngôn ngữ được xây dựng ở mức trừu tượng tương tự cho các tác vụ tương tự không có bộ tiền xử lý, hãy tham gia xem Ada.

2

Nhiều ngôn ngữ lập trình có lập trình meta cơ sở, nơi bạn viết mã cho trình biên dịch để theo dõi, thay vì môi trường thời gian chạy.

Ví dụ: trong C++, chúng tôi có các mẫu cho phép chúng tôi hướng dẫn trình biên dịch tạo mã nhất định dựa trên loại hoặc thậm chí là hằng số biên dịch. Lisp có lẽ là ví dụ nổi tiếng nhất của một ngôn ngữ với các cơ sở siêu lập trình tiên tiến.

Chỉ thị tiền xử lý/macro tiền xử lý trước chỉ là một dạng "lập trình meta" khác, mặc dù có dạng tương đối cruder hơn so với các ngôn ngữ khác. Các chỉ thị tiền xử lý hướng dẫn trình biên dịch thực hiện một số thứ nhất định tại thời gian biên dịch, chẳng hạn như bỏ qua một số mã nhất định trên các nền tảng nhất định hoặc tìm và thay thế một chuỗi trong mã bằng một chuỗi khác. Điều này sẽ không thể thực hiện khi chạy, sau khi mã của bạn đã được biên dịch.

Vì vậy, về cơ bản, bộ tiền xử lý C là một hình thức sớm của "lập trình meta" hoặc lập trình biên dịch.

+0

Hmm ... chưa bao giờ nghe nói về lập trình meta trước đây. Tôi thích cách bạn tổng quát việc sử dụng các chỉ thị tiền xử lý mặc dù. – Zaid

1

Một chút lịch sử ở đây: C++ được phát triển từ C, cần tiền xử lý nhiều hơn C++. Ví dụ, để xác định một hằng số trong C++, bạn muốn viết một cái gì đó giống như const int foo = 4;, ví dụ, thay vì #define FOO 4 đó là tương đương C thô. Thật không may, quá nhiều người mang thói quen tiền xử lý của họ từ C đến C++.

Có một số cách sử dụng hợp lý của bộ tiền xử lý trong C++. Sử dụng #include cho các tệp tiêu đề là khá cần thiết. Nó cũng hữu ích cho việc biên dịch có điều kiện, bao gồm tiêu đề bao gồm bảo vệ, vì vậy có thể #include một tiêu đề nhiều lần (chẳng hạn như trong các tiêu đề khác nhau) và chỉ xử lý một lần. Câu lệnh assert thực sự là một macro tiền xử lý và có một vài cách sử dụng tương tự.

Bên cạnh đó, có rất ít sử dụng hợp pháp trong C++.

+0

'# include' và' #ifdef ... # define' được liên kết chỉ được yêu cầu vì không phải C hoặc C++ đã thấy phù hợp để tạo ra một cơ sở giao diện thích hợp, như có thể tìm thấy trong Pascal, Modula-2, Ada và khá nhiều ngôn ngữ hiện đại. Đây chỉ là một bản tiền xử lý mà mọi người đã quen với việc sử dụng. –

Các vấn đề liên quan