2008-09-15 16 views
117

Tại sao chúng ta cần phải sử dụng:Tại sao chúng ta cần extern "C" {#include <foo.h>} trong C++?

extern "C" { 
#include <foo.h> 
} 

Cụ thể:

  • Khi chúng ta nên sử dụng nó?

  • Điều gì đang xảy ra ở trình biên dịch/trình liên kết yêu cầu chúng tôi sử dụng?

  • Cách biên dịch/liên kết thực hiện việc này giải quyết các vấn đề yêu cầu chúng tôi sử dụng?

+0

Tôi nhầm lẫn ý bạn trong tiêu đề câu hỏi của bạn ... bạn có thể xây dựng không? –

+29

Tôi không chắc chắn về cách đặt nó. Bạn đã đọc vượt quá tiêu đề? – Landon

Trả lời

106

C và C++ tương tự về mặt bề ngoài, nhưng mỗi bản dịch lại thành một bộ mã rất khác nhau. Khi bạn bao gồm một tệp tiêu đề có trình biên dịch C++, trình biên dịch đang chờ mã C++. Tuy nhiên, nếu nó là một tiêu đề C, thì trình biên dịch dự kiến ​​dữ liệu chứa trong tệp tiêu đề sẽ được biên dịch sang một định dạng nhất định — C++ 'ABI', hoặc 'Giao diện nhị phân ứng dụng', do đó trình liên kết cuộn lên. Điều này là thích hợp hơn để truyền dữ liệu C++ đến một hàm mong đợi dữ liệu C.

(Để nhận được vào thực sự nitty-gritty, C++ 's ABI thường 'Mangles' tên của chức năng của họ/các phương pháp, cho nên gọi printf() mà không suy giảm nguyên mẫu là một hàm C, C++ sẽ thực sự tạo ra mã gọi _Zprintf, cộng thêm crap vào cuối.)

Vì vậy: sử dụng extern "C" {...}; khi bao gồm tiêu đề ac — thật đơn giản. Nếu không, bạn sẽ không khớp trong mã được biên dịch và trình liên kết sẽ bị sặc. Tuy nhiên, đối với hầu hết các tiêu đề, bạn thậm chí sẽ không cần đến số extern vì hầu hết các tiêu đề C của hệ thống đều đã tính đến thực tế là chúng có thể được bao gồm bởi mã C++ và đã được mã extern của chúng.

+1

Bạn có thể giải thích chi tiết hơn về ** "hầu hết các tiêu đề C của hệ thống sẽ tính đến thực tế là chúng có thể được bao gồm bởi mã C++ và đã tồn tại mã của chúng." **? –

+3

@BulatM. Chúng chứa một cái gì đó như thế này: '#ifdef __cplusplus extern" C "{ # endif' Vì vậy, khi được bao gồm từ tệp C++, chúng vẫn được coi là tiêu đề C. – Calmarius

+1

Không ';' cần thiết ở cuối. Tôi không thể thay đổi nó, vì nó đòi hỏi ít nhất 6 ký tự để thay đổi để áp dụng thay đổi -, - ngu ngốc – danger89

13

Nó phải làm theo cách các trình biên dịch khác nhau thực hiện mang tên. Trình biên dịch C++ sẽ mang tên của một biểu tượng được xuất từ ​​tệp tiêu đề theo cách hoàn toàn khác so với trình biên dịch C, vì vậy khi bạn cố gắng liên kết, bạn sẽ nhận được một lỗi liên kết cho biết có biểu tượng bị thiếu.

Để giải quyết vấn đề này, chúng tôi yêu cầu trình biên dịch C++ chạy trong chế độ "C", vì vậy nó thực hiện việc mang tên theo cùng cách mà trình biên dịch C sẽ làm. Đã làm như vậy, các lỗi liên kết được cố định.

5

Điều này được sử dụng để giải quyết các vấn đề về mangling tên. extern C có nghĩa là các hàm nằm trong một API kiểu "phẳng" kiểu C.

6

Trình biên dịch C++ tạo tên biểu tượng khác với trình biên dịch C. Vì vậy, nếu bạn đang cố thực hiện cuộc gọi đến một hàm nằm trong tệp C, được biên dịch dưới dạng mã C, bạn cần báo cho trình biên dịch C++ biết tên biểu tượng mà nó đang cố giải quyết trông khác với mặc định; nếu không thì bước liên kết sẽ thất bại.

18

Trong C++, bạn có thể có các thực thể khác nhau dùng chung tên.Ví dụ ở đây là một danh sách các hàm tất cả các tên foo:

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

Để phân biệt giữa chúng hết, C++ sẽ tạo tên duy nhất cho mỗi cái trong một quá trình gọi là mang tên hoặc trang trí tên. C trình biên dịch không làm điều này. Hơn nữa, mỗi trình biên dịch C++ có thể làm điều này là một cách khác.

extern "C" yêu cầu trình biên dịch C++ không thực hiện bất kỳ tên mangling nào trên mã trong niềng răng. Điều này cho phép bạn gọi hàm C từ bên trong C++.

10

C và C++ có các quy tắc khác nhau về tên biểu tượng. Biểu tượng là cách trình liên kết biết rằng lệnh gọi hàm "openBankAccount" trong một tệp đối tượng được trình biên dịch tạo ra là tham chiếu đến hàm mà bạn gọi là "openBankAccount" trong một tệp đối tượng khác được tạo từ một tệp nguồn khác nhau (hoặc tương thích) trình biên dịch. Điều này cho phép bạn tạo một chương trình từ nhiều hơn một tệp nguồn, đó là một sự cứu trợ khi làm việc trên một dự án lớn.

Trong C quy tắc rất đơn giản, tất cả ký hiệu đều nằm trong một không gian tên đơn. Vì vậy, số nguyên "vớ" được lưu trữ dưới dạng "vớ" và hàm count_socks được lưu trữ dưới dạng "count_socks".

Liên kết được tạo cho C và các ngôn ngữ khác như C với quy tắc đặt tên biểu tượng đơn giản này. Vì vậy, các biểu tượng trong trình liên kết chỉ là các chuỗi đơn giản.

Nhưng trong ngôn ngữ C++, ngôn ngữ cho phép bạn có không gian tên và tính đa hình và nhiều thứ khác xung đột với quy tắc đơn giản như vậy. Tất cả sáu hàm đa hình của bạn được gọi là "add" cần phải có các ký hiệu khác nhau, hoặc các ký hiệu sai sẽ được sử dụng bởi các tệp đối tượng khác. Điều này được thực hiện bằng cách "mangling" (đó là một thuật ngữ kỹ thuật) tên của các biểu tượng.

Khi liên kết mã C++ với thư viện C hoặc mã, bạn cần phải viết "C" bất kỳ thứ gì được viết bằng C, chẳng hạn như tệp tiêu đề cho thư viện C, để trình biên dịch C++ của bạn biết rằng các tên biểu tượng này không bị xáo trộn, trong khi phần còn lại của mã C++ của bạn phải bị xáo trộn hoặc nó sẽ không hoạt động.

10

Khi nào chúng ta nên sử dụng?

Khi bạn đang liên kết libaries C vào đối tượng C++ file

gì đang xảy ra ở cấp trình biên dịch/mối liên kết đó đòi hỏi chúng ta để sử dụng nó?

C và C++ sử dụng các lược đồ khác nhau để đặt tên biểu tượng. Điều này cho phép trình liên kết sử dụng lược đồ của C khi liên kết trong thư viện đã cho.

Cách biên dịch/liên kết thực hiện việc này có giải quyết được sự cố mà yêu cầu chúng tôi sử dụng không?

Sử dụng lược đồ đặt tên C cho phép bạn tham chiếu biểu tượng kiểu C.Nếu không, trình liên kết sẽ thử các ký hiệu kiểu C++ không hoạt động.

5

Cấu trúc extern "C" {} chỉ thị cho trình biên dịch không thực hiện việc mangling trên các tên được khai báo trong niềng răng. Thông thường, trình biên dịch C++ "tăng cường" các tên hàm để chúng mã hóa thông tin kiểu về các đối số và giá trị trả về; đây được gọi là tên bị cắt . Cấu trúc extern "C" ngăn ngừa xoài.

Nó thường được sử dụng khi mã C++ cần gọi thư viện ngôn ngữ C. Nó cũng có thể được sử dụng khi trưng ra một hàm C++ (từ một DLL, ví dụ) cho các máy khách C.

104

extern "C" xác định cách biểu tượng trong tệp đối tượng được tạo nên được đặt tên. Nếu một hàm được khai báo không có "C" bên ngoài, tên biểu tượng trong tệp đối tượng sẽ sử dụng tên mang tên C++. Đây là một ví dụ.

test.c Với như vậy:

void foo() { } 

Biên soạn và niêm yết các biểu tượng trong các tập tin đối tượng cho:

$ g++ -c test.C 
$ nm test.o 
0000000000000000 T _Z3foov 
       U __gxx_personality_v0 

Hàm foo là thực sự gọi là "_Z3foov". Chuỗi này chứa thông tin kiểu cho kiểu trả về và các tham số, trong số những thứ khác. Nếu bạn thay vì viết test.c như thế này:

extern "C" { 
    void foo() { } 
} 

Sau đó biên dịch và nhìn vào biểu tượng:

$ g++ -c test.C 
$ nm test.o 
       U __gxx_personality_v0 
0000000000000000 T foo 

Bạn nhận được C liên kết. Tên của hàm "foo" trong tệp đối tượng chỉ là "foo" và nó không có tất cả các thông tin kiểu ưa thích đến từ tên mangling.

Bạn thường bao gồm tiêu đề bên trong "C" {} bên ngoài nếu mã đi kèm với nó được biên dịch bằng trình biên dịch C nhưng bạn đang cố gọi nó từ C++. Khi bạn làm điều này, bạn đang nói với trình biên dịch rằng tất cả các khai báo trong tiêu đề sẽ sử dụng liên kết C. Khi bạn liên kết mã của mình, các tệp .o của bạn sẽ chứa tham chiếu đến "foo" chứ không phải "_Z3fooblah", hy vọng sẽ khớp với bất kỳ nội dung gì trong thư viện mà bạn liên kết.

Hầu hết các thư viện hiện đại sẽ đặt nhân viên bảo vệ xung quanh các tiêu đề như vậy để biểu tượng được khai báo với liên kết phù hợp. ví dụ. trong nhiều tiêu đề chuẩn bạn sẽ tìm thấy:

#ifdef __cplusplus 
extern "C" { 
#endif 

... declarations ... 

#ifdef __cplusplus 
} 
#endif 

Điều này đảm bảo rằng khi mã C++ bao gồm tiêu đề, ký hiệu trong tệp đối tượng của bạn khớp với thư viện C. Bạn chỉ nên đặt bên ngoài "C" {} xung quanh tiêu đề C của bạn nếu nó cũ và không có các vệ sĩ này.

+2

một số ví dụ ngắn gọn là những điều thực sự tốt! – zhy

6

Bạn nên sử dụng bên ngoài "C" bất kỳ lúc nào bạn bao gồm tiêu đề xác định hàm nằm trong tệp được biên dịch bởi trình biên dịch C, được sử dụng trong tệp C++. (Nhiều thư viện C chuẩn có thể bao gồm kiểm tra này trong tiêu đề của chúng để đơn giản hơn cho nhà phát triển)

Ví dụ: nếu bạn có dự án có 3 tệp, util.c, util.h và main.cpp và cả hai các tệp .c và .cpp được biên dịch bằng trình biên dịch C++ (g ++, cc, v.v.) thì nó không thực sự cần thiết, và thậm chí có thể gây ra lỗi liên kết. Nếu quá trình xây dựng của bạn sử dụng trình biên dịch C thông thường cho util.c, thì bạn sẽ cần phải sử dụng extern "C" khi bao gồm util.h.

Điều đang xảy ra là C++ mã hóa các tham số của hàm trong tên của nó. Đây là cách quá tải chức năng hoạt động.Tất cả những gì có xu hướng xảy ra với một hàm C là bổ sung dấu gạch dưới ("_") vào đầu tên. Nếu không sử dụng extern "C", trình liên kết sẽ tìm kiếm một hàm có tên DoSomething @@ int @ float() khi tên thực tế của hàm là _DoSomething() hoặc chỉ DoSomething().

Sử dụng extern "C" giải quyết vấn đề trên bằng cách nói trình biên dịch C++ rằng nó nên tìm một hàm theo quy ước đặt tên C thay vì C++.