ผลกระทบของ“ ภายนอก” ภายนอกใน C ++ คืออะไร


1632

การใส่extern "C"รหัส C ++ ลงไปทำอะไรกันแน่?

ตัวอย่างเช่น:

extern "C" {
   void foo();
}

82
ฉันต้องการแนะนำบทความนี้ให้กับคุณ: http://www.agner.org/optimize/calling_conventions.pdfมันบอกคุณได้มากขึ้นเกี่ยวกับการเรียกการประชุมและความแตกต่างระหว่างคอมไพเลอร์
Sam Liao

1
@ Litherum ที่ด้านบนของหัวฉันจะบอกให้คอมไพเลอร์คอมไพล์ขอบเขตของโค้ดโดยใช้ C เนื่องจากคุณมี cross-compiler นอกจากนี้ยังหมายความว่าคุณมีไฟล์ Cpp ที่คุณมีfoo()ฟังก์ชั่นนั้น
ha9u63ar

1
@ ha9u63ar มันคือ 'ปิดส่วนหัวของฉัน' ความคิดเห็นที่เหลือทั้งหมดของคุณนั้นไม่ถูกต้องเช่นกัน ฉันแนะนำให้คุณลบมัน
TamaMcGlinn

คำตอบ:


1558

extern "C" ทำให้ฟังก์ชั่นชื่อใน C ++ มีการเชื่อมโยง 'C' (คอมไพเลอร์ไม่ได้ตัดชื่อ) เพื่อให้ลูกค้ารหัส C สามารถเชื่อมโยงไปยัง (เช่นใช้) ฟังก์ชั่นของคุณโดยใช้ไฟล์ส่วนหัวที่เข้ากันได้ 'C' ประกาศฟังก์ชั่นของคุณ นิยามฟังก์ชันของคุณมีอยู่ในรูปแบบไบนารี (ที่รวบรวมโดยคอมไพเลอร์ C ++ ของคุณ) ที่ตัวเชื่อมโยง 'C' จะเชื่อมโยงกับการใช้ชื่อ 'C'

เนื่องจาก C ++ มีชื่อฟังก์ชั่นมากเกินไปและ C ไม่ได้คอมไพเลอร์ C ++ จึงไม่สามารถใช้ชื่อฟังก์ชั่นเป็นรหัสเฉพาะที่จะเชื่อมโยงไปได้ดังนั้นมันจึงรวมชื่อด้วยการเพิ่มข้อมูลเกี่ยวกับอาร์กิวเมนต์ คอมไพเลอร์ AC ไม่จำเป็นต้องทำการรวมชื่อเนื่องจากคุณไม่สามารถโหลดชื่อฟังก์ชันใน C ได้มากเกินไปเมื่อคุณระบุว่าฟังก์ชันมีลิงก์ภายนอก "C" ใน C ++ คอมไพเลอร์ C ++ จะไม่เพิ่มข้อมูลประเภทอาร์กิวเมนต์ / พารามิเตอร์ให้กับชื่อที่ใช้ การเชื่อมต่อ

เพื่อให้คุณรู้ว่าคุณสามารถระบุ "C" การเชื่อมโยงไปยังแต่ละประกาศ / คำนิยามอย่างชัดเจนหรือใช้บล็อกเพื่อจัดกลุ่มลำดับของการประกาศ / คำนิยามที่จะมีการเชื่อมโยงบางอย่าง:

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

หากคุณใส่ใจในด้านเทคนิคพวกเขาจะถูกระบุไว้ในมาตรา 7.5 ของมาตรฐาน C ++ 03 ต่อไปนี้เป็นบทสรุปโดยย่อ (โดยเน้นที่ extern "C"):

  • extern "C" เป็นข้อกำหนดการเชื่อมโยง
  • คอมไพเลอร์ทุกตัวจำเป็นต้องมีลิงก์ "C"
  • ข้อมูลจำเพาะการเชื่อมโยงจะเกิดขึ้นเฉพาะในขอบเขตเนมสเปซ
  • ประเภทฟังก์ชั่นทั้งหมดชื่อฟังก์ชั่นและชื่อตัวแปรมีการเชื่อมโยงภาษา ดูความเห็นของริชาร์ด:ชื่อฟังก์ชั่นและชื่อตัวแปรที่มีการเชื่อมโยงภายนอกมีการเชื่อมโยงภาษา
  • ฟังก์ชั่นสองประเภทที่มีการเชื่อมโยงภาษาที่แตกต่างเป็นประเภทที่แตกต่างกันแม้ว่าจะเหมือนกัน
  • รายละเอียดการเชื่อมโยงซ้อนด้านหนึ่งเป็นตัวกำหนดการเชื่อมโยงสุดท้าย
  • extern "C" ถูกละเว้นสำหรับสมาชิกคลาส
  • อย่างมากที่สุดฟังก์ชันหนึ่งที่มีชื่อเฉพาะสามารถมีลิงก์ "C" ได้ (โดยไม่คำนึงถึงเนมสเปซ)
  • extern "C" บังคับให้ฟังก์ชั่นมีการเชื่อมโยงภายนอก (ไม่สามารถทำให้คงที่) ดูความคิดเห็นของ Richard: 'คงที่' ภายใน 'extern "C"' ถูกต้อง; นิติบุคคลที่ประกาศดังนั้นมีการเชื่อมโยงภายในและดังนั้นจึงไม่มีการเชื่อมโยงภาษา
  • การเชื่อมโยงจาก C ++ ไปยังวัตถุที่กำหนดไว้ในภาษาอื่นและไปยังวัตถุที่กำหนดไว้ใน C ++ จากภาษาอื่น ๆ คือการใช้งานที่กำหนดและขึ้นอยู่กับภาษา เฉพาะเมื่อกลยุทธ์เลย์เอาต์วัตถุของการใช้ภาษาสองภาษานั้นคล้ายคลึงกันมากพอที่จะสามารถเชื่อมโยงดังกล่าวได้

21
คอมไพเลอร์ C ไม่ได้ใช้ mangling ซึ่ง c ++ นั้นทำอะไร ดังนั้นหากคุณต้องการโทรหาอินเตอร์เฟส ac จากโปรแกรม c ++ คุณต้องประกาศอย่างชัดเจนว่าอินเตอร์เฟส c เป็น "extern c"
Sam Liao

58
@Faisal: อย่าพยายามลิงก์โค้ดที่สร้างด้วยคอมไพเลอร์ C ++ ที่แตกต่างกันแม้ว่าการอ้างอิงโยงจะเป็น 'extern "C" ทั้งหมด มักจะมีความแตกต่างระหว่างเลย์เอาต์ของคลาสหรือกลไกที่ใช้ในการจัดการข้อยกเว้นหรือกลไกที่ใช้เพื่อให้แน่ใจว่าตัวแปรถูกเตรียมใช้งานก่อนการใช้งานหรือความแตกต่างอื่น ๆ รวมถึงคุณอาจต้องใช้ไลบรารีการสนับสนุน แต่ละคอมไพเลอร์)
Jonathan Leffler

8
'extern "C" บังคับให้ฟังก์ชั่นมีการเชื่อมโยงภายนอก (ไม่สามารถทำให้มันคงที่)' ไม่ถูกต้อง 'คงที่' ภายใน 'ภายนอก' C "'ถูกต้อง; นิติบุคคลที่ประกาศดังนั้นมีการเชื่อมโยงภายในและดังนั้นจึงไม่มีการเชื่อมโยงภาษา
Richard Smith

14
'ทุกประเภทฟังก์ชั่นชื่อฟังก์ชั่นและชื่อตัวแปรมีการเชื่อมโยงภาษา' ก็ไม่ถูกต้องเช่นกัน ชื่อฟังก์ชั่นและชื่อตัวแปรที่มีการเชื่อมโยงภายนอกเท่านั้นที่มีการเชื่อมโยงภาษา
Richard Smith

9
โปรดทราบว่าextern "C" { int i; }เป็นคำจำกัดความ void g(char);นี้อาจจะไม่ใช่สิ่งที่คุณตั้งใจติดกับที่ไม่ใช่ความหมายของ extern "C" { extern int i; }ที่จะทำให้มันไม่ใช่ความหมายที่คุณจะต้อง ในทางตรงกันข้ามไวยากรณ์ประกาศหนึ่งโดยไม่ต้องวงเล็บปีกกาทำให้การประกาศที่ไม่ใช่คำนิยาม: extern "C" int i;เป็นเช่นเดียวกับextern "C" { extern int i; }
aschepler

327

แค่ต้องการเพิ่มข้อมูลเล็กน้อยเนื่องจากฉันยังไม่เห็นมันโพสต์

คุณมักจะเห็นรหัสในส่วนหัวของ C เช่น:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

สิ่งนี้สำเร็จคือช่วยให้คุณสามารถใช้ไฟล์ส่วนหัว C ที่มีรหัส C ++ ของคุณเพราะจะมีการกำหนดแมโคร "__cplusplus" แต่คุณยังสามารถใช้กับรหัส C เดิมได้ซึ่งไม่ได้กำหนดแมโครไว้ดังนั้นจึงไม่เห็นโครงสร้าง C ++ ที่ไม่ซ้ำกัน

แม้ว่าฉันได้เห็นรหัส C ++ เช่น:

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

ซึ่งฉันจินตนาการถึงความสำเร็จในสิ่งเดียวกัน

ไม่แน่ใจว่าวิธีไหนดีกว่า แต่ฉันได้เห็นทั้งคู่แล้ว


11
มีความแตกต่างที่ชัดเจน ในกรณีของอดีตถ้าคุณรวบรวมไฟล์นี้ด้วยคอมไพเลอร์ gcc ปกติมันจะสร้างวัตถุที่ชื่อฟังก์ชั่นไม่ได้ mangled หากคุณเชื่อมโยงวัตถุ C และ C ++ กับตัวเชื่อมโยงจะไม่พบฟังก์ชัน คุณจะต้องรวมไฟล์ "ส่วนหัวดั้งเดิม" กับคำหลักภายนอกเช่นเดียวกับในบล็อกรหัสที่สองของคุณ
Anne van Rossum

8
@Anne: คอมไพเลอร์ C ++ จะค้นหาชื่อ unmangled ด้วยเพราะเห็นextern "C"ในส่วนหัว) มันใช้งานได้ดีใช้เทคนิคนี้หลายครั้ง
Ben Voigt

20
@ แอนน์: นั่นไม่ถูกต้องคนแรกก็ดีเช่นกัน มันถูกละเว้นโดยคอมไพเลอร์ C และมีผลเช่นเดียวกับที่สองใน C ++ คอมไพเลอร์ไม่สนว่าจะเกิดขึ้นextern "C"ก่อนหรือหลังรวมส่วนหัวด้วย เมื่อถึงคอมไพเลอร์ก็เป็นเพียงข้อความสตรีมที่ประมวลผลล่วงหน้ายาวหนึ่งรายการแล้ว
Ben Voigt

8
@ ไม่เลยฉันคิดว่าคุณได้รับผลกระทบจากข้อผิดพลาดอื่น ๆ ในแหล่งที่มาเพราะสิ่งที่คุณอธิบายไม่ถูกต้อง ไม่มีรุ่นg++ใดผิดพลาดสำหรับเป้าหมายใด ๆ ในเวลาใด ๆ ในช่วง 17 ปีที่ผ่านมาอย่างน้อยที่สุด จุดรวมของตัวอย่างแรกคือมันไม่สำคัญว่าคุณจะใช้คอมไพเลอร์ C หรือ C ++ จะไม่มีการสร้างชื่อใด ๆ สำหรับชื่อในextern "C"บล็อก
Jonathan Wakely

7
"อันไหนดีกว่า" - แน่นอนตัวแปรแรกดีกว่า: อนุญาตให้รวมส่วนหัวโดยตรงโดยไม่ต้องมีข้อกำหนดเพิ่มเติมใด ๆ ทั้งในรหัส C และ C ++ วิธีที่สองเป็นวิธีแก้ปัญหาสำหรับส่วนหัวของ C ที่ผู้เขียนลืมยาม C ++ (ไม่มีปัญหาแม้ว่าจะเพิ่มสิ่งเหล่านี้ในภายหลังการประกาศ "C" ภายนอกแบบซ้อนจะได้รับการยอมรับ ... )
Aconcagua

267

ถอดรหัสg++ไบนารีที่สร้างขึ้นเพื่อดูว่าเกิดอะไรขึ้น

main.cpp

void f() {}
void g();

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

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

รวบรวมและแยกเอลฟ์เอาท์พุตที่สร้างขึ้น:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

ผลลัพธ์ประกอบด้วย:

     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

การตีความ

เราเห็นว่า:

  • efและegถูกเก็บไว้ในสัญลักษณ์ที่มีชื่อเดียวกับในรหัส

  • สัญลักษณ์อื่น ๆ ถูกทำให้ยุ่งเหยิง เรามาแก้ปัญหาให้พวกมัน

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

สรุป: ทั้งสองประเภทสัญลักษณ์ต่อไปนี้ไม่ได้ถูกทำให้ยุ่งเหยิง:

  • ที่กำหนดไว้
  • ประกาศแล้ว แต่ไม่ได้กำหนด ( Ndx = UND) เพื่อให้ที่ลิงก์หรือเวลารันจากไฟล์อ็อบเจ็กต์อื่น

ดังนั้นคุณจะต้องใช้extern "C"ทั้งคู่เมื่อโทร:

  • C จาก C ++: บอกg++ให้คาดหวังว่าจะมีสัญลักษณ์ที่ไม่มีการแกะสลักgcc
  • C ++ จาก C: บอกg++ให้สร้างสัญลักษณ์ unmangled gccเพื่อใช้

สิ่งที่ไม่ทำงานในภายนอกค

เห็นได้ชัดว่าฟีเจอร์ C ++ ใด ๆ ที่ต้องใช้ชื่อ mangling จะไม่ทำงานภายใน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 ที่รันได้น้อยที่สุดจากตัวอย่าง C ++

เพื่อความสมบูรณ์และสำหรับ newbs ออกมีดูด้วย: วิธีการใช้ไฟล์ต้นฉบับ C ในโครงการ C ++?

การเรียก C จาก C ++ นั้นง่ายมาก: ฟังก์ชัน C แต่ละตัวมีสัญลักษณ์ที่ไม่เป็นไปได้เพียงสัญลักษณ์เดียวเท่านั้นดังนั้นจึงไม่จำเป็นต้องทำงานเพิ่มเติม

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

ซีซี

#include "c.h"

int f(void) { return 1; }

วิ่ง:

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

ไม่มีextern "C"ลิงก์ล้มเหลวด้วย:

main.cpp:6: undefined reference to `f()'

เพราะg++คาดว่าจะได้พบกับ mangled fซึ่งgccไม่ได้ผลิต

ตัวอย่างบน GitHub

C ++ ที่รันได้น้อยที่สุดจากตัวอย่าง C

การเรียก C ++ จาก C นั้นยากขึ้นนิดหน่อย: เราต้องสร้างแต่ละฟังก์ชั่นที่ไม่ใช่แบบที่แยกกันของแต่ละฟังก์ชั่นที่เราต้องการเปิดเผย

ที่นี่เราแสดงให้เห็นถึงวิธีการเปิดเผยฟังก์ชั่น C ++ เกินพิกัดถึง C

main.c

#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);
}

วิ่ง:

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

ไม่extern "C"ว่าจะล้มเหลวด้วย:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

เนื่องจากg++สัญลักษณ์ที่สร้างเป็น mangled ซึ่งgccไม่สามารถหาได้

ตัวอย่างบน GitHub

ทดสอบใน Ubuntu 18.04


21
คำตอบที่ดีที่สุดเนื่องจากคุณ 1) พูดถึงอย่างชัดเจนว่าextern "C" {ช่วยให้คุณเรียกใช้ฟังก์ชัน C unmangled จากภายในโปรแกรม C ++เช่นเดียวกับฟังก์ชั่น C ++ ที่ไม่จำเป็นจากโปรแกรม Cซึ่งคำตอบอื่นไม่ชัดเจนและ 2) เพราะคุณแสดงตัวอย่างที่ชัดเจน แต่ละ. ขอบคุณ!
Gabriel Staples

3
ฉันชอบคำตอบนี้มาก
Selfboot

4
ส่งคำตอบที่ดีที่สุดเพราะจะแสดงวิธีเรียกฟังก์ชั่นโอเวอร์โหลดจาก c
Gaspa79

1
@JaveneCPPMcGowan อะไรทำให้คุณคิดว่าฉันมีครู C ++? :-)
Ciro Santilli 冠状病毒审查六四事件法轮功

205

ในทุกโปรแกรม C ++ ฟังก์ชั่นที่ไม่คงที่ทั้งหมดจะถูกแสดงในไฟล์ไบนารีเป็นสัญลักษณ์ สัญลักษณ์เหล่านี้เป็นสตริงข้อความพิเศษที่ระบุฟังก์ชันในโปรแกรมโดยเฉพาะ

ใน C ชื่อสัญลักษณ์จะเหมือนกับชื่อฟังก์ชัน สิ่งนี้เป็นไปได้เพราะใน C ไม่มีฟังก์ชั่นไม่คงที่สองตัวที่มีชื่อเดียวกัน

เนื่องจาก C ++ อนุญาตให้โหลดเกินพิกัดและมีคุณสมบัติมากมายที่ C ไม่เหมือนคลาส, ฟังก์ชันสมาชิก, ข้อมูลจำเพาะข้อยกเว้น - จึงไม่สามารถใช้ชื่อฟังก์ชันเป็นชื่อสัญลักษณ์ได้ เพื่อแก้ปัญหานั้น C ++ ใช้ชื่อที่เรียกว่า mangling ซึ่งเปลี่ยนชื่อฟังก์ชั่นและข้อมูลที่จำเป็นทั้งหมด (เช่นจำนวนและขนาดของอาร์กิวเมนต์) เป็นสตริงที่ดูแปลก ๆ ซึ่งประมวลผลโดยคอมไพเลอร์และลิงเกอร์เท่านั้น

ดังนั้นหากคุณระบุฟังก์ชั่นให้เป็น extern C คอมไพเลอร์จะไม่ทำการตั้งชื่อ mangling ด้วยมันและสามารถเข้าถึงได้โดยตรงโดยใช้ชื่อสัญลักษณ์เป็นชื่อฟังก์ชั่น

สิ่งนี้มีประโยชน์ในขณะที่ใช้งานdlsym()และdlopen()สำหรับการเรียกใช้ฟังก์ชันดังกล่าว


คุณหมายถึงอะไรโดยมีประโยชน์? symbol name = ชื่อฟังก์ชั่นจะทำให้ชื่อสัญลักษณ์ส่งผ่านไปยัง dlsym ที่รู้จักกันหรือสิ่งอื่น ๆ ?
ข้อผิดพลาด

1
@ ข้อผิดพลาด: ใช่ เป็นไปไม่ได้โดยทั่วไปในกรณีทั่วไปที่จะ dlopen () ไลบรารี C ++ ที่ใช้ร่วมกันให้เฉพาะไฟล์ส่วนหัวและเลือกฟังก์ชั่นที่เหมาะสมในการโหลด (บน x86, มีชื่อ mangling ข้อกำหนดการตีพิมพ์ในรูปแบบของ Itanium ABI ว่าทุกคอมไพเลอร์ x86 ฉันรู้ว่าการใช้งานเพื่อฉีก c ++ ชื่อฟังก์ชัน แต่ไม่มีอะไรในภาษาที่ต้องใช้นี้.)
โจนาธาน Tomer

52

ชื่อฟังก์ชั่น c ++ mangles เพื่อสร้างภาษาเชิงวัตถุจากภาษาขั้นตอน

ภาษาการเขียนโปรแกรมส่วนใหญ่ไม่ได้สร้างอยู่ด้านบนของภาษาโปรแกรมที่มีอยู่ C ++ ถูกสร้างขึ้นบน C และยิ่งไปกว่านั้นมันเป็นภาษาการเขียนโปรแกรมเชิงวัตถุที่สร้างขึ้นจากภาษาการเขียนโปรแกรมขั้นตอนและด้วยเหตุนี้จึงมีการแสดงออกของ C ++ เหมือนextern "C"ที่ให้ความเข้ากันได้กับ C ย้อนหลัง

ลองดูตัวอย่างต่อไปนี้:

#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 คอมไพเลอร์จะไม่คอมไพล์ตัวอย่างข้างต้นเนื่องจากฟังก์ชันเดียวกันprintMeถูกกำหนดสองครั้ง (แม้ว่าจะมีพารามิเตอร์ที่แตกต่างกันint avs char a)

gcc -o printMe printMe.c && ./printMe;
1 ข้อผิดพลาด PrintMe ถูกกำหนดมากกว่าหนึ่งครั้ง

คอมไพเลอร์ C ++ จะรวบรวมตัวอย่างข้างต้น มันไม่สนใจว่าprintMeจะถูกกำหนดสองครั้ง

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

นี่เป็นเพราะคอมไพเลอร์ C ++ โดยนัยเปลี่ยนชื่อฟังก์ชั่น( mangles ) ขึ้นอยู่กับพารามิเตอร์ของพวกเขา ใน C คุณสมบัตินี้ไม่รองรับ อย่างไรก็ตามเมื่อ C ++ ถูกสร้างขึ้นบน C ภาษาได้รับการออกแบบให้เน้นวัตถุและจำเป็นต้องสนับสนุนความสามารถในการสร้างคลาสที่แตกต่างด้วยเมธอด (ฟังก์ชั่น) ในชื่อเดียวกันและเพื่อแทนที่เมธอด ( เมธอดแทนที่ ) พารามิเตอร์

extern "C" พูดว่า "อย่าย่อชื่อฟังก์ชัน C"

อย่างไรก็ตามลองจินตนาการว่าเรามีไฟล์ C รุ่นเก่าที่ชื่อว่า "parent.c" ซึ่งincludeเป็นชื่อฟังก์ชั่นจากไฟล์ C รุ่นเก่าอื่น ๆ "parent.h", "child.h" ฯลฯ หากไฟล์เดิม "parent.c" ทำงาน ผ่านคอมไพเลอร์ C ++ จากนั้นชื่อฟังก์ชั่นจะถูกจัดการและพวกเขาจะไม่ตรงกับชื่อฟังก์ชั่นที่ระบุใน "parent.h", "child.h" เป็นต้น - ดังนั้นชื่อฟังก์ชันในไฟล์ภายนอกเหล่านั้นจะต้อง แหลกเหลว Mangling ชื่อฟังก์ชั่นในโปรแกรม C ที่ซับซ้อนผู้ที่มีการพึ่งพาจำนวนมากสามารถนำไปสู่รหัสที่เสีย ดังนั้นจึงสะดวกที่จะระบุคีย์เวิร์ดที่สามารถบอกคอมไพเลอร์ C ++ ว่าจะไม่ทำให้ชื่อฟังก์ชันยุ่งเหยิง

extern "C"คำหลักที่บอกว่าเป็น C ++ คอมไพเลอร์ไม่ได้ชื่อฉีก (เปลี่ยนชื่อ) C ฟังก์ชั่น

ตัวอย่างเช่น:

extern "C" void printMe(int a);


เราไม่สามารถใช้extern "C"ถ้าเรามีเพียงdllไฟล์? ฉันหมายความว่าถ้าเราไม่มีไฟล์ส่วนหัวและเพิ่งมีไฟล์ต้นฉบับ (เพิ่งใช้งาน) และการใช้ฟังก์ชั่นผ่านตัวชี้ฟังก์ชั่น ในสถานะนี้เราเพิ่งใช้ฟังก์ชั่น (โดยไม่คำนึงถึงชื่อ)
BattleTested

@tfmontague สำหรับฉันคุณจับมันถูกต้อง! ตรงไปที่หัว
Artanis Zeratul

29

C-header ใด ๆ ไม่สามารถใช้งานร่วมกับ C ++ ได้โดยการรวมอยู่ใน "C" ภายนอก เมื่อตัวระบุใน C-header ขัดแย้งกับคำสำคัญ C ++ คอมไพเลอร์ C ++ จะบ่นเกี่ยวกับเรื่องนี้

ตัวอย่างเช่นฉันเห็นรหัสต่อไปนี้ล้มเหลวใน g ++:

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

Kinda เข้าท่า แต่เป็นสิ่งที่ควรคำนึงถึงเมื่อทำการย้าย C-code ไปที่ C ++


14
extern "C"หมายถึงการใช้การเชื่อมโยง C ตามที่อธิบายโดยคำตอบอื่น ๆ ไม่ได้หมายความว่า "รวบรวมเนื้อหาเป็น C" หรืออะไรก็ตาม int virtual;ไม่ถูกต้องใน C ++ และการระบุลิงก์ที่แตกต่างกันจะไม่เปลี่ยนแปลง
MM

1
... หรือโหมดโดยทั่วไปรหัสใด ๆ ที่มีข้อผิดพลาดทางไวยากรณ์จะไม่รวบรวม
Valentin Heinitz

4
@ValentinHeinitz ตามธรรมชาติแม้ว่าการใช้ "virtual" เป็นตัวระบุใน C ไม่ใช่ข้อผิดพลาดทางไวยากรณ์ ฉันแค่อยากจะชี้ให้เห็นว่าคุณไม่สามารถใช้ส่วนหัว C ใด ๆใน C ++ โดยอัตโนมัติโดยวาง extern "C" ไว้รอบ ๆ
Sander Mertens

28

มันเปลี่ยนแปลงการเชื่อมโยงของการทำงานในลักษณะดังกล่าวว่าการทำงานเป็น callable จากซีในทางปฏิบัตินั่นหมายความว่าชื่อฟังก์ชันไม่ได้เป็นmangled


3
Mangled เป็นคำที่ใช้โดยทั่วไป ... อย่าเชื่อว่าฉันเคยเห็น 'ตกแต่ง' ที่ใช้กับความหมายนี้
Matthew Scharley

1
Microsoft (อย่างน้อยบางส่วน) ใช้การตกแต่งมากกว่าที่จัดไว้ในเอกสารของพวกเขา พวกเขายังตั้งชื่อเครื่องมือของพวกเขาเพื่อ undecorate (aka ยกเลิกการฉีก) undnameชื่อ
René Nyffenegger

20

มันแจ้งให้คอมไพเลอร์ C ++ เพื่อค้นหาชื่อของฟังก์ชั่นเหล่านั้นในรูปแบบ C เมื่อทำการเชื่อมโยงเพราะชื่อของฟังก์ชั่นที่คอมไพล์ใน C และ C ++ จะแตกต่างกันระหว่างขั้นตอนการเชื่อมโยง


12

extern "C" หมายถึงการได้รับการยอมรับโดยคอมไพเลอร์ C ++ และเพื่อแจ้งให้คอมไพเลอร์ทราบว่าฟังก์ชั่นที่บันทึกไว้ (หรือจะ) คอมไพล์ในสไตล์ C ดังนั้นในขณะที่เชื่อมโยงมันจะเชื่อมโยงไปยังรุ่นที่ถูกต้องของฟังก์ชันจาก C


6

ฉันใช้ 'extern "C"' มาก่อนสำหรับไฟล์ dll (dynamic link library) เพื่อทำฟังก์ชั่นหลัก () ฟังก์ชั่น "exportable" ดังนั้นมันสามารถใช้งานได้ในภายหลังใน dll ที่สามารถเรียกใช้งานได้ บางทีตัวอย่างที่ฉันเคยใช้อาจมีประโยชน์

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);
}

4
ปลอม. extern "C"และ__declspec(dllexport)ไม่เกี่ยวข้อง การควบคุมการตกแต่งสัญลักษณ์ในอดีตนั้นส่วนหลังมีหน้าที่สร้างรายการส่งออก คุณสามารถส่งออกสัญลักษณ์โดยใช้การตกแต่งชื่อ C ++ ได้เช่นกัน นอกเหนือจากจุดที่ขาดหายไปของคำถามนี้อย่างสมบูรณ์แล้วยังมีข้อผิดพลาดอื่น ๆ ในตัวอย่างโค้ดด้วย สำหรับหนึ่งmainส่งออกจาก DLL ของคุณไม่ได้ประกาศค่าตอบแทน หรือเรียกแบบแผนสำหรับเรื่องนั้น เมื่อนำเข้าคุณจะกำหนดแผนการโทรแบบสุ่ม ( WINAPI) และใช้สัญลักษณ์ที่ไม่ถูกต้องสำหรับบิลด์ 32 บิต (ควรเป็น_mainหรือ_main@0) ขออภัย -1
IIsspectable

1
ซ้ำแล้วซ้ำอีกที่คุณไม่รู้ว่าคุณกำลังทำอะไร แต่การทำแบบนี้ดูเหมือนจะใช้ได้ผลสำหรับคุณสำหรับรายการแพลตฟอร์มเป้าหมายที่ยังไม่เปิดเผย คุณไม่ได้แก้ไขปัญหาที่ฉันพูดถึงในความคิดเห็นก่อนหน้าของฉัน สิ่งนี้ยังคงเป็นการลงคะแนนเนื่องจากความผิดอย่างรุนแรง (มีมากกว่านั้นไม่เหมาะกับความคิดเห็นเดียว)
IIsspectable

1
การโพสต์คำตอบเกี่ยวกับประเภทของ Stack Overflow หมายถึงคุณรู้ว่าคุณกำลังทำอะไรอยู่ สิ่งนี้คาดว่า สำหรับความพยายามของคุณ"เพื่อป้องกันความเสียหายของสแต็กเมื่อเรียกใช้" : ลายเซ็นฟังก์ชันของคุณระบุค่าส่งคืนประเภทvoid*แต่การใช้งานของคุณจะไม่ส่งคืนสิ่งใด มันจะบินได้ดีจริงๆ ...
IInspectable

1
หากคุณใช้บางสิ่งบางอย่างที่ปรากฏในการทำงานโดยโชคบริสุทธิ์แล้วคุณได้อย่างชัดเจนไม่ได้รู้ว่าสิ่งที่คุณกำลังทำ (ของคุณ"การทำงาน"ตัวอย่างตกอยู่ในประเภทนั้น) มันเป็นพฤติกรรมที่ไม่ได้กำหนดและดูเหมือนว่าจะทำงานเป็นรูปแบบที่ถูกต้องของพฤติกรรมที่ไม่ได้กำหนด มันยังไม่ได้กำหนด ฉันขอขอบคุณอย่างสูงหากคุณใช้ความขยันมากขึ้นในอนาคต ส่วนหนึ่งอาจลบคำตอบที่เสนอนี้ได้
IIsspectable

1
คุณกำลังตีความฟังก์ชันที่ไม่ส่งคืนสิ่งใดเป็นฟังก์ชันที่ส่งกลับตัวชี้อีกครั้ง มันเป็นโชคที่บริสุทธิ์ที่ x86 นั้นให้อภัยอย่างมากเกี่ยวกับลายเซ็นของฟังก์ชั่นที่ไม่ตรงกันและโดยเฉพาะอย่างยิ่งค่าตอบแทนประเภทหนึ่ง รหัสของคุณทำงานโดยบังเอิญเท่านั้น หากคุณไม่เห็นด้วยคุณต้องอธิบายว่าทำไมรหัสของคุณถึงทำงานได้อย่างน่าเชื่อถือ
IIsspectable

5

extern "C"เป็นข้อกำหนดการเชื่อมโยงที่ใช้ในการเรียกใช้ฟังก์ชัน Cในไฟล์ที่มาภ เราสามารถเรียกใช้ฟังก์ชัน C, เขียนตัวแปรและรวมส่วนหัว ฟังก์ชั่นถูกประกาศในนิติบุคคลภายนอก & มันถูกกำหนดไว้ภายนอก ไวยากรณ์คือ

ประเภท 1:

extern "language" function-prototype

ประเภทที่ 2:

extern "language"
{
     function-prototype
};

เช่น:

#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)
{
    //
    //
}

3

คำตอบนี้มีไว้สำหรับผู้ที่ใจร้อน / มีกำหนดส่งงานให้พบเพียงคำอธิบายเพียงส่วนเดียวเท่านั้น:

  • ใน C ++ คุณสามารถมีชื่อเดียวกันในชั้นเรียนผ่านการโหลดมากเกินไป (เช่นเนื่องจากพวกเขาเป็นชื่อเดียวกันทั้งหมดไม่สามารถส่งออกตาม - เป็นจาก dll ฯลฯ ) วิธีการแก้ปัญหาเหล่านี้คือพวกเขาจะถูกแปลงเป็นสตริงที่แตกต่างกัน ) สัญลักษณ์บัญชีชื่อของฟังก์ชั่นเช่นเดียวกับข้อโต้แย้งดังนั้นแต่ละฟังก์ชั่นเหล่านี้แม้จะมีชื่อเดียวกันสามารถระบุได้ไม่ซ้ำกัน (เรียกอีกอย่างว่าชื่อ mangling)
  • ใน C คุณไม่มีการโหลดมากเกินไปชื่อฟังก์ชั่นไม่ซ้ำกัน (ดังนั้นสตริงที่แยกต่างหากสำหรับการระบุชื่อฟังก์ชั่นที่ไม่ซ้ำกันไม่จำเป็นดังนั้นสัญลักษณ์เป็นชื่อฟังก์ชั่นของตัวเอง)

ดังนั้น
ใน C ++ ที่มีชื่อ mangling identities ที่ไม่ซ้ำกันแต่ละฟังก์ชัน
ใน C แม้ว่าจะไม่มีชื่อ mangling identities ที่ไม่ซ้ำกันแต่ละฟังก์ชัน

ในการเปลี่ยนพฤติกรรมของ C ++ นั่นคือเพื่อระบุว่าชื่อ mangling ไม่ควรเกิดขึ้นสำหรับฟังก์ชั่นหนึ่งคุณสามารถใช้extern "C"ก่อนชื่อฟังก์ชั่นไม่ว่าด้วยเหตุผลใดก็ตามเช่นการส่งออกฟังก์ชั่นที่มีชื่อเฉพาะจาก dll สำหรับใช้งานโดยลูกค้า

อ่านคำตอบอื่น ๆ สำหรับคำตอบที่ละเอียดมากขึ้น / ถูกต้องมากขึ้น


1

เมื่อผสม C และ C ++ (เช่นการเรียกใช้ฟังก์ชัน C จาก C ++; และ b. การเรียกใช้ฟังก์ชัน C ++ จาก C) ชื่อ C ++ จะทำให้เกิดปัญหาในการเชื่อมโยง ในทางเทคนิคแล้วปัญหานี้เกิดขึ้นเฉพาะเมื่อฟังก์ชั่น callee ได้รับการรวบรวมเป็นไบนารี่ (ส่วนใหญ่แล้วเป็นไฟล์ไลบรารี * .a) โดยใช้คอมไพเลอร์ที่เกี่ยวข้อง

ดังนั้นเราต้องใช้ extern "C" เพื่อปิดการใช้งานชื่อ mangling ใน C ++


0

โดยไม่ขัดแย้งกับคำตอบที่ดีอื่น ๆ ฉันจะเพิ่มตัวอย่างของฉันเล็กน้อย

อะไรที่คอมไพเลอร์ C ++ทำ: มันจะรวบรวมชื่อในกระบวนการรวบรวมดังนั้นเราจึงต้องการให้คอมไพเลอร์ปฏิบัติต่อ Cการนำไปใช้เป็นพิเศษ

เมื่อเราสร้างคลาส C ++ และเพิ่มextern "C"เราจะบอกคอมไพเลอร์ C ++ ของเราว่าเรากำลังใช้หลักการเรียก C

เหตุผล (เรากำลังเรียกใช้ C จาก C ++): เราต้องการเรียกใช้ฟังก์ชัน C จาก C ++ หรือเรียกใช้ฟังก์ชัน C ++ จาก C (C ++ คลาส ... ฯลฯ ไม่ทำงานใน C)


ยินดีต้อนรับสู่ Stack Overflow หากคุณตัดสินใจที่จะตอบคำถามที่เก่ากว่าซึ่งมีคำตอบที่ถูกต้องและถูกต้องแล้วการเพิ่มคำตอบใหม่ในช่วงปลายวันอาจไม่ได้รับเครดิตใด ๆ หากคุณมีข้อมูลใหม่ที่โดดเด่นหรือคุณมั่นใจว่าคำตอบอื่น ๆ นั้นผิดทั้งหมดโดยเพิ่มคำตอบใหม่ แต่ 'อีกคำตอบ' ให้ข้อมูลพื้นฐานเดียวกันเป็นเวลานานหลังจากที่คำถามถูกถามมักจะ ' คุณไม่ได้รับเครดิตมาก ตรงไปตรงมาฉันไม่คิดว่ามีอะไรใหม่ในคำตอบนี้
Jonathan Leffler

ฉันควรจะจำประเด็นของคุณได้แล้ว - โอเค
Susobhan Das

-1

ฟังก์ชั่นเป็นโมฆะ f () รวบรวมโดยคอมไพเลอร์ C และฟังก์ชั่นที่มีชื่อเดียวกันเป็นโมฆะ f () รวบรวมโดยคอมไพเลอร์ C ++ ไม่ได้เป็นฟังก์ชั่นเดียวกัน หากคุณเขียนฟังก์ชันนั้นใน C แล้วลองโทรจาก C ++ ตัวลิงก์จะมองหาฟังก์ชัน C ++ และไม่พบฟังก์ชัน C

extern "C" บอกคอมไพเลอร์ C ++ ว่าคุณมีฟังก์ชั่นที่คอมไพล์โดยคอมไพเลอร์ C เมื่อคุณบอกว่ามันถูกคอมไพล์โดยคอมไพเลอร์ C คอมไพเลอร์ C ++ จะรู้วิธีเรียกมันอย่างถูกต้อง

นอกจากนี้ยังอนุญาตให้คอมไพเลอร์ C ++ สามารถรวบรวมฟังก์ชัน C ++ ในลักษณะที่คอมไพเลอร์ C สามารถเรียกใช้ได้ ฟังก์ชันนั้นจะเป็นฟังก์ชัน C อย่างเป็นทางการ แต่เนื่องจากคอมไพเลอร์รวบรวมโดย C ++ จึงสามารถใช้คุณลักษณะ C ++ ทั้งหมดและมีคำหลัก C ++ ทั้งหมด


คอมไพเลอร์ C ++ สามารถรวบรวมextern "C"ฟังก์ชั่น - และ (ภายใต้ข้อ จำกัด บางอย่าง) มันจะสามารถเรียกใช้งานได้โดยโค้ดที่คอมไพล์โดยคอมไพเลอร์ C
Jonathan Leffler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.