Câu hỏi Hiệu ứng của "C" bên ngoài trong C ++ là gì?


Chính xác những gì đang đưa extern "C" vào mã C ++ làm gì?

Ví dụ:

extern "C" {
   void foo();
}

1219
2018-06-25 02:10


gốc


Tôi muốn giới thiệu cho bạn bài viết này: http://www.agner.org/optimize/calling_conventions.pdf Nó cho bạn biết nhiều hơn về quy ước gọi và sự khác biệt giữa các trình biên dịch. - Sam Liao
@Litherum Trên đỉnh đầu của tôi, nó đang nói cho trình biên dịch biên dịch phạm vi mã đó bằng cách sử dụng C, cho rằng bạn có một trình biên dịch chéo. Ngoài ra, nó có nghĩa là bạn có một tập tin Cpp, nơi bạn có foo() chức năng. - ha9u63ar


Các câu trả lời:


extern "C" làm cho một tên hàm trong C ++ có liên kết 'C' (trình biên dịch không mang tên) để mã C của khách hàng có thể liên kết tới (tức là sử dụng) hàm của bạn bằng tệp tiêu đề tương thích 'C' có chứa chỉ khai báo chức năng của bạn. Định nghĩa hàm của bạn được chứa trong một định dạng nhị phân (được biên dịch bởi trình biên dịch C ++ của bạn) mà trình liên kết 'C' của khách hàng sau đó sẽ liên kết với việc sử dụng tên 'C'.

Vì C ++ có quá tải tên hàm và C không, trình biên dịch C ++ không thể chỉ sử dụng tên hàm làm id duy nhất để liên kết, vì vậy nó mang tên bằng cách thêm thông tin về các đối số. Trình biên dịch AC không cần xé tên vì bạn không thể quá tải tên hàm trong C. Khi bạn nói rằng một hàm có liên kết ngoài "C" trong C ++, trình biên dịch C ++ không thêm thông tin kiểu tham số / tham số vào tên được sử dụng cho liên kết.

Để bạn biết, bạn có thể chỉ định liên kết "C" cho mỗi khai báo / định nghĩa riêng lẻ một cách rõ ràng hoặc sử dụng một khối để nhóm một chuỗi các khai báo / định nghĩa để có một liên kết nhất định:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

Nếu bạn quan tâm đến các kỹ thuật, chúng được liệt kê trong phần 7.5 của tiêu chuẩn C ++ 03, đây là một bản tóm tắt ngắn gọn (với sự nhấn mạnh vào extern "C"):

  • extern "C" là một đặc tả liên kết
  • Mỗi trình biên dịch là cần thiết để cung cấp liên kết "C"
  • một đặc tả liên kết chỉ xảy ra trong phạm vi không gian tên
  •  tất cả các loại hàm, tên hàm và tên biến có liên kết ngôn ngữ   Xem bình luận của Richard: Chỉ các tên hàm và tên biến có liên kết bên ngoài mới có liên kết ngôn ngữ
  • hai loại chức năng với các liên kết ngôn ngữ riêng biệt là các loại riêng biệt ngay cả khi nếu không giống hệt nhau
  • liên kết thông số kỹ thuật tổ, bên trong một xác định liên kết cuối cùng
  • extern "C" bị bỏ qua cho các thành viên trong lớp
  • nhiều nhất một chức năng với một tên cụ thể có thể có liên kết "C" (bất kể không gian tên)
  •  extern "C" buộc một hàm có liên kết bên ngoài (không thể làm cho nó tĩnh)    Xem bình luận của Richard:    'static' bên trong 'extern "C"' là hợp lệ; một thực thể được khai báo có liên kết nội bộ và do đó không có liên kết ngôn ngữ
  • Liên kết từ C ++ với các đối tượng được định nghĩa trong các ngôn ngữ khác và các đối tượng được định nghĩa trong C ++ từ các ngôn ngữ khác được thực hiện theo định nghĩa và phụ thuộc vào ngôn ngữ. Chỉ khi mà các chiến lược bố trí đối tượng của hai triển khai ngôn ngữ là đủ tương tự thì có thể liên kết như vậy

1219
2018-06-25 02:12



Trình biên dịch C không sử dụng xoài mà c ++ làm. Vì vậy, nếu bạn muốn gọi một giao diện c từ một chương trình c ++, bạn phải khai báo rõ ràng rằng giao diện c là "extern c". - Sam Liao
@Faisal: không cố gắng liên kết mã được xây dựng với các trình biên dịch C ++ khác nhau, ngay cả khi các tham chiếu chéo đều là 'extern' C ''. Thường có sự khác nhau giữa bố cục lớp hoặc cơ chế được sử dụng để xử lý ngoại lệ hoặc cơ chế được sử dụng để đảm bảo biến được khởi tạo trước khi sử dụng hoặc các khác biệt như vậy, cộng với bạn có thể cần hai thư viện hỗ trợ thời gian chạy C ++ riêng biệt mỗi trình biên dịch). - Jonathan Leffler
@Leffler - cảm ơn, bạn làm cho điểm tốt. Tôi không có ý định khuyến khích sử dụng các trình biên dịch C ++ khác nhau bằng cách sử dụng extern "C". Thay vào đó, tôi đã hy vọng đề nghị rằng nếu bạn không viết một cái gì đó mà sẽ cần phải được liên kết với một trình biên dịch C ++, bạn có thể không cần extern "C". - Faisal Vali
'extern "C" buộc một hàm có liên kết bên ngoài (không thể làm cho nó tĩnh)' là không chính xác. 'static' bên trong 'extern "C"' là hợp lệ; một thực thể được khai báo có liên kết nội bộ và do đó không có liên kết ngôn ngữ. - Richard Smith
'tất cả các loại hàm, tên hàm và tên biến có liên kết ngôn ngữ' cũng không chính xác. Chỉ các tên hàm và tên biến có liên kết bên ngoài mới có liên kết ngôn ngữ. - Richard Smith


Chỉ muốn thêm một chút thông tin, vì tôi chưa thấy nó được đăng.

Bạn sẽ thường thấy mã trong các tiêu đề C như sau:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Điều này hoàn thành là nó cho phép bạn sử dụng tệp tiêu đề C đó với mã C ++ của bạn, vì macro "__cplusplus" sẽ được định nghĩa. Nhưng bạn có thể cũng thế vẫn sử dụng nó với mã C cũ của bạn, trong đó macro KHÔNG PHẢI được định nghĩa, vì vậy nó sẽ không thấy cấu trúc C ++ duy nhất.

Mặc dù, tôi cũng đã thấy mã C ++ như:

extern "C" {
#include "legacy_C_header.h"
}

mà tôi hình dung hoàn thành nhiều điều tương tự.

Không chắc chắn cách nào tốt hơn, nhưng tôi đã thấy cả hai.


244
2017-10-21 01:08



Có một sự khác biệt rõ rệt. Trong trường hợp trước đây, nếu bạn biên dịch tập tin này với trình biên dịch gcc bình thường, nó sẽ tạo ra một đối tượng mà tên hàm không bị xáo trộn. Nếu bạn sau đó liên kết các đối tượng C và C ++ với trình liên kết, nó sẽ KHÔNG tìm thấy các hàm. Bạn sẽ cần phải bao gồm các tệp "tiêu đề cũ" đó với từ khóa bên ngoài như trong khối mã thứ hai của bạn. - Anne van Rossum
@ Anne: Trình biên dịch C ++ cũng sẽ tìm kiếm các tên chưa được sắp xếp, bởi vì nó đã thấy extern "C" trong tiêu đề). Nó hoạt động rất tốt, sử dụng kỹ thuật này nhiều lần. - Ben Voigt
@ Anne: Điều đó không đúng, cái đầu tiên cũng ổn. Nó bị bỏ qua bởi trình biên dịch C, và có tác dụng tương tự như phần thứ hai trong C ++. Trình biên dịch không thể quan tâm ít hơn nếu nó gặp extern "C" trước hoặc sau nó bao gồm tiêu đề. Bởi thời gian nó đạt đến trình biên dịch, nó chỉ là một dòng dài của văn bản preprocessed anyway. - Ben Voigt
@ Anne, không, tôi nghĩ rằng bạn đã bị ảnh hưởng bởi một số lỗi khác trong nguồn, bởi vì những gì bạn mô tả là sai. Không có phiên bản g++ đã sai, đối với bất kỳ mục tiêu nào, vào bất kỳ thời điểm nào trong vòng 17 năm qua. Toàn bộ điểm của ví dụ đầu tiên là việc bạn sử dụng trình biên dịch C hay C ++ không quan trọng, không có tên mangling nào được thực hiện cho các tên trong extern "C" khối. - Jonathan Wakely
"cái nào tốt hơn" - chắc chắn, biến thể đầu tiên là tốt hơn: Nó cho phép bao gồm tiêu đề trực tiếp, không có bất kỳ yêu cầu nào khác, cả trong mã C và C ++. Cách tiếp cận thứ hai là một giải pháp cho các tiêu đề C, tác giả quên các bộ bảo vệ C ++ (không có vấn đề gì, tuy nhiên, nếu chúng được thêm vào sau đó, các khai báo "C" bên ngoài lồng nhau được chấp nhận ...). - Aconcagua


Trong mọi chương trình C ++, tất cả các hàm không tĩnh được biểu diễn trong tệp nhị phân dưới dạng biểu tượng. Các ký hiệu này là các chuỗi văn bản đặc biệt nhận dạng duy nhất một chức năng trong chương trình.

Trong C, tên biểu tượng giống với tên hàm. Điều này là có thể bởi vì trong C không có hai hàm không tĩnh có thể có cùng tên.

Bởi vì C ++ cho phép quá tải và có nhiều tính năng mà C không giống như các lớp, các hàm thành viên, các đặc tả ngoại lệ - không thể đơn giản sử dụng tên hàm làm tên biểu tượng. Để giải quyết điều đó, C ++ sử dụng cái gọi là mangling tên, biến đổi tên hàm và tất cả các thông tin cần thiết (như số lượng và kích thước của các đối số) thành chuỗi ký tự lạ được xử lý chỉ bởi trình biên dịch và trình liên kết.

Vì vậy, nếu bạn chỉ định một hàm là extern C, trình biên dịch sẽ không thực hiện việc mang tên với nó và nó có thể trực tiếp được truy cập bằng tên biểu tượng của nó làm tên hàm.

Điều này có ích trong khi sử dụng dlsym() và dlopen() để gọi các chức năng như vậy.


170
2018-06-25 05:22



ý bạn là gì? là tên biểu tượng = tên chức năng sẽ làm cho tên biểu tượng được truyền cho dlsym được biết đến, hoặc điều khác? - Error
@Error: có. Về cơ bản, điều này là không thể trong trường hợp chung đối với dlopen () một thư viện chia sẻ C ++ chỉ được cung cấp một tệp tiêu đề và chọn hàm đúng để tải. (Trên x86, có một đặc tả mangling được công bố dưới dạng Itanium ABI mà tất cả các trình biên dịch x86 mà tôi biết sử dụng cho các tên hàm mangle C ++, nhưng không có gì trong ngôn ngữ yêu cầu này.) - Jonathan Tomer


Hãy dịch ngược tệp đối tượng g ++ được tạo để xem điều gì xảy ra bên trong việc triển khai này.

Tạo ví dụ

Đầu vào:

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Biên dịch với đầu ra ELF GCC 4.8 Linux:

g++ -c a.cpp

Biên dịch lại bảng biểu tượng:

readelf -s a.o

Đầu ra chứa:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

Diễn dịch

Chúng ta thấy rằng:

  • ef và eg được lưu trữ trong các ký hiệu có cùng tên như trong mã

  • các biểu tượng khác bị xé. Hãy tháo gỡ chúng:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Kết luận: cả hai loại ký hiệu sau đây đều là không phải bị cắt xén:

  • xác định
  • đã khai báo nhưng chưa được xác định (Ndx = UND), được cung cấp tại liên kết hoặc thời gian chạy từ một tệp đối tượng khác

Vì vậy, bạn sẽ cần extern "C" cả khi gọi:

  • C từ C ++: cho biết g++ để mong đợi các biểu tượng không bị xáo trộn được tạo ra bởi gcc
  • C ++ từ C: tell g++ để tạo các biểu tượng chưa được sửa đổi cho gcc sử dụng

Những thứ không hoạt động trong extern C

Nó trở nên rõ ràng rằng bất kỳ tính năng C ++ nào yêu cầu mangling tên sẽ không bị vướng vào bên trong extern C:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

122
2018-05-29 10:06





C ++ mangles tên hàm để tạo một ngôn ngữ hướng đối tượng từ một ngôn ngữ thủ tục

Hầu hết các ngôn ngữ lập trình không được xây dựng trên các ngôn ngữ lập trình hiện có. C ++ được xây dựng trên đầu trang của C, và hơn nữa nó là một ngôn ngữ lập trình hướng đối tượng được xây dựng từ một ngôn ngữ lập trình thủ tục, và vì lý do đó có các từ khóa C ++ như extern cung cấp khả năng tương thích ngược với C.

Hãy xem ví dụ sau:

#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

Trình biên dịch C sẽ không biên dịch ví dụ trên, bởi vì cùng một hàm printMe được định nghĩa hai lần (mặc dù chúng có các tham số khác nhau int a so với char a).

gcc -o printMe printMe.c && ./printMe;
1 lỗi. PrintMe được xác định nhiều lần.

Trình biên dịch C ++ sẽ biên dịch ví dụ trên. Nó không quan tâm printMe được định nghĩa hai lần.

g ++ -o printMe printMe.c && ./printMe;

Điều này là do trình biên dịch C ++ ngầm đổi tên (mangles) các chức năng dựa trên các thông số của chúng. Trong C, tính năng này không được hỗ trợ. Tuy nhiên, khi C ++ được xây dựng trên C, ngôn ngữ được thiết kế hướng đối tượng và cần hỗ trợ khả năng tạo các lớp khác nhau với các phương thức (hàm) cùng tên và ghi đè các phương thức (phương pháp ghi đè) dựa trên các thông số khác nhau.

Extern nói "không mangle tên hàm"

Tuy nhiên, hãy tưởng tượng chúng tôi có một tệp C cũ có tên "parent.c" includes tên hàm từ các tệp C cũ khác, "parent.h", "child.h", v.v. Nếu tệp "parent.c" cũ được chạy thông qua trình biên dịch C ++, thì tên hàm sẽ bị xáo trộn và chúng sẽ không còn khớp với tên hàm được chỉ định trong "parent.h", "child.h", v.v. - do đó, tên hàm trong các tệp bên ngoài đó cũng sẽ cần phải được xáo trộn. Và điều này có thể trở nên khá lộn xộn. Vì vậy, nó có thể là thuận tiện để cung cấp một từ khóa mà có thể nói với trình biên dịch C ++ không mangle một tên chức năng.

Các extern từ khóa nói với một trình biên dịch C ++ không để mangle (đổi tên) tên hàm. Sử dụng ví dụ: extern void printMe(int a);


24
2018-02-12 01:50





Nó thay đổi liên kết của một hàm theo cách mà hàm có thể gọi được từ C. Trong thực tế điều đó có nghĩa là tên hàm không phải là xảo quyệt.


22
2018-06-25 02:12



bị xáo trộn hoặc trang trí thuật ngữ thích hợp là gì? - ojblass
Bị xáo trộn là thuật ngữ thường được sử dụng ... Đừng tin rằng tôi từng thấy 'trang trí' được sử dụng với ý nghĩa này. - Matthew Scharley
đây được gọi là trang trí en.wikipedia.org/wiki/Name_mangling - Error


Không phải bất kỳ tiêu đề C nào sẽ biên dịch với "C" bên ngoài. Khi các định danh trong một C-header xung đột với các từ khóa C ++ thì trình biên dịch C ++ sẽ phàn nàn về điều này.

Ví dụ, tôi đã thấy mã sau không thành công trong một g ++:

extern "C" {
struct method {
    int virtual;
};
}

Kinda có ý nghĩa, nhưng là một cái gì đó để ghi nhớ khi chuyển C-code để C + +.


21
2018-01-09 22:16



extern "C" có nghĩa là sử dụng liên kết C, như được mô tả bằng các câu trả lời khác. Nó không có nghĩa là "biên dịch nội dung như C" hay bất cứ thứ gì. int virtual; không hợp lệ trong C ++ và chỉ định liên kết khác nhau không thay đổi điều đó. - M.M
... hoặc chế độ nói chung, bất kỳ mã nào có lỗi cú pháp sẽ không biên dịch. - Valentin Heinitz
@ValentinHeinitz tự nhiên, mặc dù sử dụng "ảo" như một định danh trong C không phải là một lỗi cú pháp. Tôi chỉ muốn nói rằng bạn không thể tự động sử dụng bất kì C tiêu đề trong C ++ bằng cách đặt extern "C" xung quanh nó. - Sander Mertens