Chính xác những gì đưa extern "C"
vào mã C ++ làm gì?
Ví dụ:
extern "C" {
void foo();
}
foo()
chức năng đó .
Chính xác những gì đưa extern "C"
vào mã C ++ làm gì?
Ví dụ:
extern "C" {
void foo();
}
foo()
chức năng đó .
Câu trả lời:
extern "C" tạo một tên hàm trong C ++ có liên kết 'C' (trình biên dịch không xáo trộn tên) để mã C của máy khách có thể liên kết đến (tức là sử dụng) hàm của bạn bằng tệp tiêu đề tương thích 'C' chỉ chứa tuyên bố 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 để sử dụng tên 'C'.
Do C ++ có quá tải tên hàm và C thì 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, do đó, nó xáo trộn 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 phải xáo trộn 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 "C" bên ngoài trong C ++, trình biên dịch C ++ không thêm thông tin loại 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 rõ ràng liên kết "C" cho từng khai báo / định nghĩa riêng lẻ 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 (nhấn mạnh vào "C" bên ngoài):
extern "C" { int i; }
là một định nghĩa. Đây có thể không phải là những gì bạn dự định, bên cạnh định nghĩa không void g(char);
. Để làm cho nó không định nghĩa, bạn sẽ cần extern "C" { extern int i; }
. Mặt khác, cú pháp một khai báo không có dấu ngoặc làm cho khai báo không định nghĩa: extern "C" int i;
giống nhưextern "C" { extern int i; }
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ẽ rất thường thấy mã trong các tiêu đề C như vậy:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Điều này thực hiện được là nó cho phép bạn sử dụng tệp tiêu đề C đó với mã C ++ của mình, vì macro "__cplusplus" sẽ được xác định. Nhưng bạn có thể cũng vẫn sử dụng nó với mã di sản C của bạn, nơi mà các vĩ mô là không xác định, vì vậy nó sẽ không nhìn thấy duy nhất C ++ xây dựng.
Mặc dù, tôi cũng đã thấy mã C ++ như:
extern "C" {
#include "legacy_C_header.h"
}
mà tôi tưởng tượng hoàn thành nhiều điều tương tự.
Không chắc cách nào tốt hơn, nhưng tôi đã thấy cả hai.
extern "C"
trong tiêu đề). Nó hoạt động tuyệt vời, sử dụng kỹ thuật này nhiều lần.
extern "C"
trước hay sau khi nó bao gồm tiêu đề. Vào thời điểm nó đến trình biên dịch, dù sao nó cũng chỉ là một dòng văn bản được xử lý trước.
g++
có lỗi này, cho bất kỳ mục tiêu nào, tại bất kỳ thời điểm nào trong ít nhất 17 năm qua. Điểm chung của ví dụ đầu tiên là không quan trọng bạn sử dụng trình biên dịch C hay C ++, việc xáo trộn tên sẽ không được thực hiện cho các tên trong extern "C"
khối.
Biên dịch một g++
nhị phân được tạo để xem những gì đang xảy ra
main.cpp
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à tháo rời đầu ra ELF được tạo :
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
Đầu ra chứa:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 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 được đọc sai Chúng ta 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 không được đọc sai:
Ndx = UND
), được cung cấp tại liên kết hoặc thời gian chạy từ tệp đối tượng khácVì vậy, bạn sẽ cần extern "C"
cả hai khi gọi:
g++
để mong đợi các biểu tượng không bị thay đổi được tạo bởigcc
g++
cầu tạo các ký hiệu không thay đổi gcc
để sử dụngNhững thứ không hoạt động ở bên ngoài C
Rõ ràng là bất kỳ tính năng C ++ nào yêu cầu xáo trộn tên sẽ không hoạt động 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) { }
}
C runnable tối thiểu từ ví dụ C ++
Để hoàn thiện và cho các newbs ngoài kia, hãy xem thêm: Làm thế nào để sử dụng các tệp nguồn C trong một dự án C ++?
Gọi C từ C ++ khá dễ dàng: mỗi hàm C chỉ có một biểu tượng không bị sai lệch, do đó không cần phải làm thêm.
main.cpp
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
ch
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++
* because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
cc
#include "c.h"
int f(void) { return 1; }
Chạy:
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out
Không có extern "C"
liên kết thất bại với:
main.cpp:6: undefined reference to `f()'
bởi vì g++
hy vọng sẽ tìm thấy một máng xối f
, mà gcc
không sản xuất.
C ++ tối thiểu có thể chạy được từ ví dụ C
Gọi C ++ từ C khó hơn một chút: chúng ta phải tự tạo các phiên bản không bị xáo trộn của từng chức năng mà chúng ta muốn phơi bày.
Ở đây chúng tôi minh họa cách phơi bày quá tải chức năng C ++ cho C.
C chính
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
cpp.h
#ifndef CPP_H
#define CPP_H
#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif
#endif
cpp.cpp
#include "cpp.h"
int f(int i) {
return i + 1;
}
int f(float i) {
return i + 2;
}
int f_int(int i) {
return f(i);
}
int f_float(float i) {
return f(i);
}
Chạy:
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out
Không có extern "C"
nó thất bại với:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
bởi vì g++
tạo ra các biểu tượng mangled mà gcc
không thể tìm thấy.
Đã thử nghiệm trong Ubuntu 18.04.
extern "C" {
giúp bạn gọi các hàm C không bị biến dạng từ trong các chương trình C ++ , cũng như các hàm C ++ không bị biến dạng từ trong các chương trình C , mà các câu trả lời khác không quá rõ ràng, và 2) vì bạn hiển thị các ví dụ khác biệt về mỗi. Cảm ơn!
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 các ký hiệu. Các ký hiệu này là các chuỗi văn bản đặc biệt xác định duy nhất một chức năng trong chương trình.
Trong C, tên biểu tượng giống như 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 nào 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 - như các lớp, hàm thành viên, đặ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à xáo trộn 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 một chuỗi trông 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 xáo trộn tên với nó và nó có thể được truy cập trực tiếp bằng cách sử dụng tên ký hiệu 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.
Hầu hết các ngôn ngữ lập trình không được xây dựng dựa trên các ngôn ngữ lập trình hiện có. C ++ được xây dựng trên đỉnh C và hơn nữa, nó là ngôn ngữ lập trình hướng đối tượng được xây dựng từ ngôn ngữ lập trình thủ tục và vì lý do đó, có các biểu thức C ++ giống như extern "C"
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;
}
AC trình biên dịch sẽ không biên dịch ví dụ trên, vì các chức năng tương tự printMe
được định nghĩa hai lần (mặc dù họ có thông số khác nhau int a
vs char a
).
gcc -o printMe printMe.c && ./printMe;
1 lỗi. PrintMe được định nghĩa nhiều lần.
Một trình biên dịch C ++ sẽ biên dịch ví dụ trên. Nó không quan tâm printMe
được xác định hai lần.
g ++ -o printMe printMe.c && ./printMe;
Điều này là do trình biên dịch C ++ hoàn toàn đổi tên các hàm ( mangles ) dựa trên các tham 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ế theo hướng đối tượng và cần hỗ trợ khả năng tạo các lớp khác nhau bằng các phương thức (hàm) cùng tên và ghi đè các phương thức (ghi đè phương thức ) dựa trên khác nhau thông số.
extern "C"
nói "không mang tên hàm C"Tuy nhiên, hãy tưởng tượng chúng ta có một tệp C kế thừa có tên là "Parent.c" include
tên hàm từ các tệp C kế thừa khác, "Parent.h", "child.h", v.v ... Nếu tệp "Parent.c" kế thừa được chạy thông qua trình biên dịch C ++, sau đó các tên hàm sẽ được xáo trộn và chúng sẽ không còn khớp với các tên hàm được chỉ định trong "Parent.h", "child.h", v.v. - vì vậy tên hàm trong các tệp bên ngoài đó cũng cần phải được đọc sai Tên hàm của Mangling trong một chương trình C phức tạp, những tên có nhiều phụ thuộc, có thể dẫn đến mã bị hỏng; vì vậy có thể thuận tiện khi cung cấp một từ khóa có thể báo cho trình biên dịch C ++ không sử dụng tên hàm.
Các extern "C"
từ khóa kể một trình biên dịch C ++ không mangle (đổi tên) C chức năng tên.
Ví dụ:
extern "C" void printMe(int a);
extern "C"
nếu chúng ta chỉ có một dll
tập tin? Ý tôi là nếu chúng ta không có tệp tiêu đề và chỉ có tệp nguồn (chỉ triển khai) và sử dụng chức năng của nó thông qua con trỏ hàm. ở trạng thái này, chúng ta chỉ sử dụng các hàm (bất kể tên của nó).
Không phải bất kỳ tiêu đề C nào cũng có thể tương thích với C ++ bằng cách chỉ gói trong "C" bên ngoài. Khi các định danh trong xung đột tiêu đề C với các từ khóa C ++, trình biên dịch C ++ sẽ phàn nàn về điều này.
Ví dụ, tôi đã thấy đoạn mã sau thất bại trong g ++:
extern "C" {
struct method {
int virtual;
};
}
Kinda có ý nghĩa, nhưng là điều cần lưu ý khi chuyển mã C sang C ++.
extern "C"
có nghĩa là sử dụng liên kết C, như được mô tả bởi các câu trả lời khác. Nó không có nghĩa là "biên dịch nội dung là C" hoặc bất cứ điều 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 đó.
Nó thay đổi liên kết của 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 bị sai lệch .
undname
.
Nó thông báo cho trình biên dịch C ++ tìm kiếm tên của các hàm đó theo kiểu C khi liên kết, vì tên của các hàm được biên dịch trong C và C ++ khác nhau trong giai đoạn liên kết.
Tôi đã sử dụng 'extern "C"' trước đây cho các tệp dll (thư viện liên kết động) để tạo ra hàm main () "có thể xuất" để nó có thể được sử dụng sau này trong một tệp thực thi khác từ dll. Có lẽ một ví dụ về nơi tôi từng sử dụng nó có thể hữu ích.
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
extern "C"
và __declspec(dllexport)
không liên quan. Các điều khiển trang trí biểu tượng trước, sau này chịu trách nhiệm tạo ra một mục xuất. Bạn cũng có thể xuất một biểu tượng bằng cách sử dụng trang trí tên C ++. Bên cạnh việc hoàn toàn thiếu điểm của câu hỏi này, có những lỗi khác trong mẫu mã. Đối với một, main
xuất từ DLL của bạn không khai báo giá trị trả về. Hoặc gọi hội nghị, cho vấn đề đó. Khi nhập, bạn gán một quy ước gọi ngẫu nhiên ( WINAPI
) và sử dụng ký hiệu sai cho các bản dựng 32 bit (nên là _main
hoặc _main@0
). Xin lỗi, -1.
void*
, nhưng việc triển khai của bạn không trả về bất cứ điều gì. Nó sẽ bay rất tốt ...
extern "C"
là một đặc tả liên kết được sử dụng để gọi các hàm C trong các tệp nguồn Cpp . Chúng ta có thể gọi các hàm C, viết biến và bao gồm các tiêu đề . Hàm được khai báo trong thực thể bên ngoài & nó được định nghĩa bên ngoài. Cú pháp là
Loại 1:
extern "language" function-prototype
Loại 2:
extern "language"
{
function-prototype
};
ví dụ:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
Câu trả lời này dành cho những người thiếu kiên nhẫn / có thời hạn để đáp ứng, chỉ có một phần / giải thích đơn giản dưới đây:
Vì vậy,
trong C ++, với việc xáo trộn tên nhận dạng duy nhất từng chức năng
trong C, thậm chí không có tên xáo trộn nhận dạng duy nhất mỗi chức năng
Để thay đổi hành vi của C ++, nghĩa là, để chỉ định việc xáo trộn tên đó không nên xảy ra đối với một chức năng cụ thể, bạn có thể sử dụng extern "C" trước tên hàm, vì bất kỳ lý do gì, như xuất một hàm có tên cụ thể từ dll , để sử dụng bởi khách hàng của nó.
Đọc các câu trả lời khác, để có câu trả lời chi tiết hơn / chính xác hơn.
Khi trộn C và C ++ (nghĩa là gọi hàm C từ C ++; và gọi hàm C ++ từ C), việc xáo trộn tên C ++ gây ra vấn đề liên kết. Về mặt kỹ thuật, vấn đề này chỉ xảy ra khi các hàm callee đã được biên dịch thành tệp nhị phân (rất có thể là tệp thư viện * .a) bằng trình biên dịch tương ứng.
Vì vậy, chúng ta cần sử dụng extern "C" để vô hiệu hóa tên xáo trộn trong C ++.
Không xung đột với các câu trả lời tốt khác, tôi sẽ thêm một chút ví dụ của tôi.
Chính xác Trình biên dịch C ++ làm gì: nó xử lý các tên trong quá trình biên dịch, do đó chúng tôi yêu cầu trình biên dịch xử lý C
việc thực hiện một cách đặc biệt.
Khi chúng tôi tạo các lớp C ++ và thêm extern "C"
, chúng tôi sẽ nói với trình biên dịch C ++ của mình rằng chúng tôi đang sử dụng quy ước gọi C.
Lý do (chúng tôi đang gọi triển khai C từ C ++): hoặc chúng tôi muốn gọi hàm C từ C ++ hoặc gọi hàm C ++ từ C (các lớp C ++ ... vv không hoạt động trong C).
Hàm void f () được biên dịch bởi trình biên dịch C và hàm có cùng tên void f () được biên dịch bởi trình biên dịch C ++ không cùng chức năng. Nếu bạn đã viết hàm đó trong C, và sau đó bạn đã cố gắng gọi nó từ C ++, thì trình liên kết sẽ tìm hàm C ++ và không tìm thấy hàm C.
extern "C" cho trình biên dịch C ++ biết rằng bạn có một hàm được biên dịch bởi trình biên dịch C. Một khi bạn nói với nó rằng nó được biên dịch bởi trình biên dịch C, trình biên dịch C ++ sẽ biết cách gọi nó một cách chính xác.
Nó cũng cho phép trình biên dịch C ++ biên dịch hàm C ++ theo cách mà trình biên dịch C có thể gọi nó. Hàm đó sẽ chính thức là hàm C, nhưng do nó được biên dịch bởi trình biên dịch C ++, nên nó có thể sử dụng tất cả các tính năng của C ++ và có tất cả các từ khóa C ++.
extern "C"
hàm - và (chịu một số ràng buộc) nó sẽ có thể gọi được bằng mã được biên dịch bởi trình biên dịch C.