ข้อผิดพลาดของการอ้างอิงที่ไม่ได้กำหนด / การแก้ไขสัญลักษณ์ภายนอกที่ไม่ได้กำหนดคืออะไรและฉันจะแก้ไขได้อย่างไร


1493

ข้อผิดพลาดของการอ้างอิงที่ไม่ได้กำหนด / การอ้างอิงสัญลักษณ์ภายนอกที่ไม่ได้แก้ไขคืออะไร สาเหตุที่พบบ่อยคืออะไรและจะแก้ไข / ป้องกันได้อย่างไร

รู้สึกอิสระที่จะแก้ไข / เพิ่มของคุณเอง


@LuchianGrigore 'อย่าลังเลที่จะเพิ่มคำตอบ'ฉันต้องการเพิ่มลิงก์ที่เกี่ยวข้อง (IMHO) คำตอบหลักของคุณหากคุณต้องการอนุญาต
πάνταῥεῖ

19
คำถามนี้กว้างเกินไปที่จะยอมรับคำตอบที่มีประโยชน์ ฉันไม่อยากจะเชื่อเลยว่ามีคนมีชื่อเสียงมากกว่า 1,000 คนที่แสดงความคิดเห็นในเรื่องนี้โดยไม่ต้องตั้งคำถาม ในระหว่างนี้ฉันเห็นคำถามมากมายที่สมเหตุสมผลและมีประโยชน์มากสำหรับฉันที่ถูกปิด
Albert van der Horst

10
@AlbertvanderHorst "คำถามนี้กว้างเกินไปที่จะยอมรับคำตอบที่มีประโยชน์" คำตอบด้านล่างไม่มีประโยชน์หรือไม่?
LF

4
อาจเป็นประโยชน์เช่นเดียวกับคำตอบมากมายสำหรับคำถามที่ถูกตั้งค่าสถานะเป็นกว้างเกินไป
Albert van der Horst

1
ฉันต้องการเห็นตัวอย่างที่ทำซ้ำได้น้อยที่สุดเป็นสิ่งที่เราขอจากผู้ใช้ใหม่ส่วนใหญ่อย่างสุจริต ฉันไม่ได้หมายถึงอะไรเลยมันเป็นเพียง - เราไม่สามารถคาดหวังให้ผู้คนปฏิบัติตามกฎที่เราไม่ได้ทำเพื่อตนเอง
Danilo

คำตอบ:


848

การคอมไพล์โปรแกรม C ++ นั้นเกิดขึ้นในหลายขั้นตอนตามที่ระบุโดย2.2 (ให้เครดิต Keith Thompson สำหรับการอ้างอิง) :

ความสำคัญในหมู่กฎไวยากรณ์ของการแปลจะถูกระบุโดยขั้นตอนต่อไปนี้[ดูเชิงอรรถ]

  1. อักขระของแฟ้มต้นฉบับทางกายภาพถูกแมปในลักษณะที่กำหนดการนำไปใช้กับชุดอักขระของแหล่งข้อมูลพื้นฐาน (แนะนำอักขระบรรทัดใหม่สำหรับตัวบ่งชี้การสิ้นสุดของบรรทัด) หากจำเป็น [SNIP]
  2. แต่ละอินสแตนซ์ของอักขระแบ็กสแลช (\) ตามด้วยอักขระบรรทัดใหม่ทันทีจะถูกลบออกทำให้เกิดการเชื่อมต่อกับแหล่งที่มาทางกายภาพเพื่อสร้างบรรทัดของแหล่งที่มาทางตรรกะ [SNIP]
  3. ไฟล์ต้นฉบับจะถูกแยกออกเป็นโทเค็นการประมวลผลล่วงหน้า (2.5) และลำดับของอักขระช่องว่าง (รวมถึงความคิดเห็น) [SNIP]
  4. การประมวลผลคำสั่งล่วงหน้าจะถูกดำเนินการการเรียกใช้แมโครจะถูกขยายและนิพจน์โอเปอเรเตอร์ของ _Pragma unary จะถูกดำเนินการ [SNIP]
  5. อักขระต้นทางแต่ละชุดเป็นสมาชิกในตัวอักษรตัวอักษรหรือสตริงตัวอักษรเช่นเดียวกับแต่ละลำดับหนีและชื่อตัวละครสากลในตัวอักษรตัวอักษรหรือตัวอักษรสตริงที่ไม่ดิบจะถูกแปลงเป็นสมาชิกที่สอดคล้องกันของชุดอักขระการดำเนินการ; [SNIP]
  6. โทเค็นตัวอักษรของสตริงที่อยู่ติดกันจะถูกตัดแบ่ง
  7. อักขระช่องว่างที่แยกโทเค็นไม่สำคัญอีกต่อไป โทเค็นการประมวลผลล่วงหน้าแต่ละรายการจะถูกแปลงเป็นโทเค็น (2.7) โทเค็นที่ได้จะถูกวิเคราะห์และแปลและแปลเป็นหน่วยการแปล [SNIP]
  8. หน่วยการแปลที่แปลและหน่วยการสร้างอินสแตนซ์จะรวมกันดังนี้: [SNIP]
  9. การอ้างอิงเอนทิตีภายนอกทั้งหมดได้รับการแก้ไขแล้ว คอมโพเนนต์ไลบรารีถูกลิงก์เพื่อตอบสนองการอ้างอิงภายนอกไปยังเอนทิตีที่ไม่ได้กำหนดไว้ในการแปลปัจจุบัน เอาต์พุตตัวแปลทั้งหมดดังกล่าวจะถูกรวบรวมไว้ในอิมเมจโปรแกรมซึ่งมีข้อมูลที่จำเป็นสำหรับการดำเนินการในสภาพแวดล้อมการทำงาน (เน้นที่เหมือง)

[เชิงอรรถ]การติดตั้งใช้งานจะต้องทำตัวราวกับว่าขั้นตอนที่แยกต่างหากเหล่านี้เกิดขึ้นแม้ว่าในทางปฏิบัติขั้นตอนที่แตกต่างกันอาจถูกพับเข้าด้วยกัน

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

สมมติว่าคุณกำหนดสัญลักษณ์ในa a.cppตอนนี้b.cpp ประกาศสัญลักษณ์นั้นและใช้มัน ก่อนที่จะทำการเชื่อมโยงเพียงแค่สมมติว่าสัญลักษณ์นั้นถูกกำหนดไว้ที่ไหนสักแห่งแต่มันยังไม่สนใจว่าจะอยู่ที่ไหน ขั้นตอนการเชื่อมโยงมีหน้าที่ในการค้นหาสัญลักษณ์และเชื่อมโยงอย่างถูกต้องกับb.cpp(จริง ๆ แล้วกับวัตถุหรือไลบรารีที่ใช้)

หากคุณใช้ Microsoft Visual Studio คุณจะเห็นว่าโครงการสร้าง.libไฟล์ สิ่งเหล่านี้มีตารางของสัญลักษณ์ที่ส่งออกและตารางของสัญลักษณ์ที่นำเข้า สัญลักษณ์ที่นำเข้าจะได้รับการแก้ไขกับไลบรารีที่คุณเชื่อมโยงและสัญลักษณ์ที่ส่งออกจะมีให้สำหรับห้องสมุดที่ใช้งานนั้น.lib(ถ้ามี)

มีกลไกที่คล้ายกันสำหรับคอมไพเลอร์ / แพลตฟอร์มอื่น ๆ

ข้อความผิดพลาดที่พบบ่อยerror LNK2001, error LNK1120, error LNK2019สำหรับMicrosoft Visual Studioและundefined reference to symbolnameสำหรับGCC

รหัส:

struct X
{
   virtual void foo();
};
struct Y : X
{
   void foo() {}
};
struct A
{
   virtual ~A() = 0;
};
struct B: A
{
   virtual ~B(){}
};
extern int x;
void foo();
int main()
{
   x = 0;
   foo();
   Y y;
   B b;
}

จะสร้างข้อผิดพลาดต่อไปนี้ด้วยGCC :

/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status

และข้อผิดพลาดที่คล้ายกันกับMicrosoft Visual Studio :

1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals

สาเหตุที่พบบ่อย ได้แก่ :


16
โดยส่วนตัวแล้วฉันคิดว่าข้อความแสดงข้อผิดพลาด MS linker นั้นสามารถอ่านได้เหมือนกับข้อผิดพลาดของ GCC พวกเขายังมีข้อได้เปรียบของการรวมทั้งชื่อ mangled และ unmangled สำหรับภายนอกที่ไม่ได้รับการแก้ไข การมีชื่อ mangled จะมีประโยชน์เมื่อคุณต้องการดูไลบรารีหรือไฟล์วัตถุโดยตรงเพื่อดูว่าปัญหาอาจเกิดขึ้น (ตัวอย่างเช่นการเรียกประชุมที่ไม่ตรงกัน) นอกจากนี้ฉันไม่แน่ใจว่า MSVC รุ่นใดที่ทำให้เกิดข้อผิดพลาดที่นี่ แต่รุ่นที่ใหม่กว่านั้นมีชื่อ (ทั้งสองภาษาและไม่ได้แยก) ของฟังก์ชันที่อ้างถึงสัญลักษณ์ภายนอกที่ไม่ได้แก้ไข
Michael Burr

5
เดวิดรส์เขียนบทความดีดีเกี่ยวกับวิธีการทำงาน Linkers: คู่มือการเริ่มต้นของการ Linkers จากหัวข้อของคำถามนี้ฉันคิดว่ามันอาจเป็นประโยชน์
Pressacco

@TankorSmash ใช้ gcc หรือไม่ MinGW แม่นยำยิ่งขึ้น
doug65536

1
@luchian มันจะดีถ้าคุณเพิ่มถูกต้องแก้ไขข้อผิดพลาดดังกล่าวข้างต้น
Phalgun

177

สมาชิกชั้นเรียน:

virtualdestructor ที่บริสุทธิ์ต้องมีการนำไปปฏิบัติ

การประกาศ destructor pure ยังคงต้องการให้คุณกำหนด (ต่างจากฟังก์ชั่นทั่วไป):

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

สิ่งนี้เกิดขึ้นเพราะ destructors คลาสฐานถูกเรียกเมื่อวัตถุถูกทำลายโดยปริยายดังนั้นจึงจำเป็นต้องมีคำจำกัดความ

virtual เมธอดต้องถูกนำมาใช้หรือกำหนดเป็น pure

สิ่งนี้คล้ายกับvirtualวิธีที่ไม่มีคำนิยามโดยมีเหตุผลเพิ่มเติมว่าการประกาศล้วนสร้าง vummy vummy และคุณอาจได้รับข้อผิดพลาด linker โดยไม่ต้องใช้ฟังก์ชัน:

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

เพื่อการทำงานให้ประกาศX::foo()อย่างบริสุทธิ์

struct X
{
    virtual void foo() = 0;
};

ไม่ใช่virtualสมาชิกระดับ

สมาชิกบางคนจำเป็นต้องกำหนดแม้ว่าจะไม่ได้ใช้อย่างชัดเจน:

struct A
{ 
    ~A();
};

ต่อไปนี้จะทำให้เกิดข้อผิดพลาด:

A a;      //destructor undefined

การใช้งานสามารถเป็นแบบอินไลน์ในการกำหนดชั้นเรียนของตัวเอง:

struct A
{ 
    ~A() {}
};

หรือนอก:

A::~A() {}

หากการใช้งานอยู่นอกข้อกำหนดคลาส แต่ในส่วนหัววิธีการจะต้องมีการทำเครื่องหมายเป็นinlineเพื่อป้องกันไม่ให้หลายคำนิยาม

วิธีสมาชิกทั้งหมดที่ใช้จะต้องมีการกำหนดถ้าใช้

ข้อผิดพลาดทั่วไปคือลืมชื่อ:

struct A
{
   void foo();
};

void foo() {}

int main()
{
   A a;
   a.foo();
}

ความหมายควรจะเป็น

void A::foo() {}

staticสมาชิกข้อมูลต้องถูกกำหนดนอกคลาสในหน่วยการแปลเดียว :

struct X
{
    static int x;
};
int main()
{
    int x = X::x;
}
//int X::x; //uncomment this line to define X::x

initializer สามารถจัดเตรียมไว้สำหรับstatic constสมาชิกข้อมูลประเภท integral หรือ enumeration ภายในการกำหนดคลาส; อย่างไรก็ตามการใช้งานของสมาชิกรายนี้จะยังคงต้องการคำจำกัดความขอบเขตของเนมสเปซตามที่อธิบายไว้ข้างต้น C ++ 11 อนุญาตให้เริ่มต้นภายในชั้นเรียนสำหรับstatic constสมาชิกข้อมูลทั้งหมด


แค่คิดว่าคุณอาจต้องการเน้นว่าการทำทั้งสองอย่างนั้นเป็นไปได้และ dtor ไม่ใช่ข้อยกเว้น (มันไม่ชัดเจนจากถ้อยคำของคุณในแวบแรก)
Deduplicator

112

ไม่สามารถลิงก์กับไลบรารี / อ็อบเจ็กต์ไฟล์ที่เหมาะสมหรือไฟล์การนำไปปฏิบัติ

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

ภายใต้gccคุณจะต้องระบุไฟล์วัตถุทั้งหมดที่จะเชื่อมโยงเข้าด้วยกันในบรรทัดคำสั่งหรือรวบรวมไฟล์การนำไปใช้ร่วมกัน

g++ -o test objectFile1.o objectFile2.o -lLibraryName

libraryNameนี่เป็นเพียงชื่อเปลือยของห้องสมุดโดยไม่ต้องเพิ่มเติมเฉพาะแพลตฟอร์ม ดังนั้นเช่นบน Linux ไฟล์ห้องสมุดมักจะเรียกว่าแต่คุณต้องการเพียงเขียนlibfoo.so -lfooใน Windows อาจมีไฟล์ชื่อfoo.libเดียวกัน แต่คุณใช้อาร์กิวเมนต์เดียวกัน -L‹directory›คุณอาจจะต้องเพิ่มไดเรกทอรีที่สามารถพบแฟ้มที่ใช้ ให้แน่ใจว่าจะไม่เขียนวรรคหลังหรือ-l-L

สำหรับXCode : เพิ่มเส้นทางการค้นหาส่วนหัวของผู้ใช้ -> เพิ่มเส้นทางการค้นหาห้องสมุด -> ลากและวางการอ้างอิงไลบรารีจริงลงในโฟลเดอร์โครงการ

ภายใต้MSVSไฟล์ที่เพิ่มไปยังโปรเจ็กต์จะมีไฟล์ออบเจ็กต์เชื่อมโยงเข้าด้วยกันโดยอัตโนมัติและlibไฟล์จะถูกสร้างขึ้น (ในการใช้งานทั่วไป) หากต้องการใช้สัญลักษณ์ในโครงการแยกต่างหากคุณจะต้องรวมlibไฟล์ในการตั้งค่าโครงการ ซึ่งจะดำเนินการในส่วนของคุณสมบัติ Linker Input -> Additional Dependenciesโครงการใน ( libควรเพิ่มพา ธ ไปยังไฟล์Linker -> General -> Additional Library Directories) เมื่อใช้ไลบรารีของบุคคลที่สามที่ให้มาพร้อมกับlibไฟล์ความล้มเหลวในการทำเช่นนั้นมักส่งผลให้เกิดข้อผิดพลาด

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

ในการเขียนโปรแกรมของ Windows __imp_เครื่องหมายบอกเล่าเรื่องราวที่คุณไม่ได้เชื่อมโยงห้องสมุดที่จำเป็นก็คือชื่อของสัญลักษณ์ที่ได้รับการแก้ไขเริ่มต้นด้วย ค้นหาชื่อของฟังก์ชั่นในเอกสารประกอบและควรบอกว่าคุณต้องใช้ไลบรารีใด ตัวอย่างเช่น MSDN จะใส่ข้อมูลลงในกล่องที่ด้านล่างของแต่ละฟังก์ชันในส่วนที่เรียกว่า "Library"


1
มันจะดีถ้าคุณสามารถครอบคลุมข้อผิดพลาดทั่วไปgcc main.cแทนgcc main.c other.c (ซึ่งผู้เริ่มต้นมักจะทำก่อนที่โครงการของพวกเขาจะมีขนาดใหญ่พอที่จะสร้างไฟล์. o)
MM

101

ประกาศ แต่ไม่ได้กำหนดตัวแปรหรือฟังก์ชั่น

การประกาศตัวแปรทั่วไปคือ

extern int x;

เนื่องจากนี่เป็นเพียงการประกาศจึงจำเป็นต้องมีคำนิยามเดียว คำนิยามที่สอดคล้องกันจะเป็น:

int x;

ตัวอย่างเช่นต่อไปนี้จะสร้างข้อผิดพลาด:

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition

หมายเหตุที่คล้ายกันใช้กับฟังก์ชั่น ประกาศฟังก์ชั่นโดยไม่กำหนดมันนำไปสู่ข้อผิดพลาด:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition

ระวังว่าฟังก์ชั่นที่คุณใช้นั้นตรงกับฟังก์ชั่นที่คุณประกาศ ตัวอย่างเช่นคุณอาจมีคุณสมบัติ CV-qualifier ไม่ตรงกัน:

void foo(int& x);
int main()
{
   int x;
   foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
                          //for void foo(int& x)

ตัวอย่างอื่น ๆ ที่ไม่ตรงกัน ได้แก่

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

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


ใน VS ไฟล์ cpp ที่ตรงกับส่วนหัวที่#includesไม่ได้ถูกเพิ่มลงในไดเรกทอรีต้นทางจะตกอยู่ภายใต้หมวดหมู่ของคำจำกัดความที่ขาดหายไป
ลอรีสเติ

86

ลำดับที่ระบุไลบรารีที่เชื่อมโยงซึ่งกันและกันซึ่งเป็นสิ่งที่ผิด

ลำดับการเชื่อมโยงไลบรารีจะมีความสำคัญหากห้องสมุดขึ้นอยู่กับแต่ละอื่น ๆ โดยทั่วไปถ้าห้องสมุดAขึ้นอยู่กับห้องสมุดBแล้วlibA ต้องปรากฏขึ้นก่อนlibBในธงลิงเกอร์

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

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};

สร้างห้องสมุด:

$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o

รวบรวม:

$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out

ดังนั้นจะทำซ้ำอีกครั้งสั่งซื้อไมว่า!


ฉันสงสัยความจริงก็คือในกรณีของฉันฉันมีไฟล์วัตถุหนึ่งไฟล์ที่ขึ้นอยู่กับห้องสมุดสาธารณะ ฉันต้องแก้ไข Makefile และวางไลบรารีหลังจากวัตถุด้วย gcc 4.8.4 บนเดเบียน บน Centos 6.5 with gcc 4.4 Makefile ทำงานได้โดยไม่มีปัญหา
Marco Sulla

74

"การอ้างอิงที่ไม่ได้กำหนด / สัญลักษณ์ภายนอกที่ไม่ได้แก้ไข" คืออะไร

ฉันจะพยายามอธิบายว่า "การอ้างอิงที่ไม่ได้กำหนด / สัญลักษณ์ภายนอกที่ยังไม่ได้แก้ไข" คืออะไร

หมายเหตุ: ฉันใช้ g ++ และ Linux และตัวอย่างทั้งหมดเป็นของมัน

ตัวอย่างเช่นเรามีรหัส

// src1.cpp
void print();

static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;

int main()
{
    print();
    return 0;
}

และ

// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
//extern int local_var_name;

void print ()
{
    // printf("%d%d\n", global_var_name, local_var_name);
    printf("%d\n", global_var_name);
}

สร้างไฟล์วัตถุ

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o

หลังจากเฟสแอสเซมเบลอร์เรามีไฟล์วัตถุซึ่งมีสัญลักษณ์ใด ๆ ที่จะส่งออก ดูสัญลักษณ์

$ readelf --symbols src1.o
  Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL14local_var_name # [1]
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_var_name     # [2]

ฉันได้ปฏิเสธบางบรรทัดจากผลลัพธ์เนื่องจากไม่สำคัญ

ดังนั้นเราจะเห็นสัญลักษณ์ติดตามเพื่อส่งออก

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable

src2.cpp ส่งออกอะไรและเราไม่เห็นสัญลักษณ์ใด ๆ

เชื่อมโยงไฟล์วัตถุของเรา

$ g++ src1.o src2.o -o prog

และเรียกใช้

$ ./prog
123

Linker เห็นสัญลักษณ์ที่ส่งออกและเชื่อมโยง ตอนนี้เราพยายาม uncomment บรรทัดใน src2.cpp เหมือนที่นี่

// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
extern int local_var_name;

void print ()
{
    printf("%d%d\n", global_var_name, local_var_name);
}

และสร้างอ็อบเจ็กต์ไฟล์อีกครั้ง

$ g++ -c src2.cpp -o src2.o

ตกลง (ไม่มีข้อผิดพลาด) เนื่องจากเราสร้างไฟล์วัตถุเท่านั้นการเชื่อมโยงยังไม่เสร็จสิ้น ลองลิงค์

$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status

มันเกิดขึ้นเพราะ local_var_name ของเราคงที่นั่นคือไม่สามารถมองเห็นได้สำหรับโมดูลอื่น ๆ ตอนนี้ลึกซึ้งยิ่งขึ้น รับเอาต์พุตเฟสการแปล

$ g++ -S src1.cpp -o src1.s

// src1.s
look src1.s

    .file   "src1.cpp"
    .local  _ZL14local_var_name
    .comm   _ZL14local_var_name,4,4
    .globl  global_var_name
    .data
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; assembler code, not interesting for us
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

ดังนั้นเราจึงเห็นว่าไม่มีป้ายกำกับสำหรับ local_var_name นั่นเป็นสาเหตุที่ตัวเชื่อมโยงไม่พบ แต่เราเป็นแฮ็กเกอร์ :) และเราสามารถแก้ไขได้ เปิด src1.s ในโปรแกรมแก้ไขข้อความและเปลี่ยนแปลง

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

ถึง

    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789

เช่นคุณควรมีด้านล่าง

    .file   "src1.cpp"
    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789
    .globl  global_var_name
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; ...

เราได้เปลี่ยนการมองเห็นของ local_var_name และตั้งค่าเป็น 456789 ลองสร้างไฟล์วัตถุจากมัน

$ g++ -c src1.s -o src2.o

ตกลงดูเอาต์พุต readelf (สัญลักษณ์)

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

ตอนนี้ local_var_name มีการผูก GLOBAL (เดิมคือ LOCAL)

ลิงค์

$ g++ src1.o src2.o -o prog

และเรียกใช้

$ ./prog 
123456789

ตกลงเราแฮ็คมัน :)

ดังนั้นผลลัพธ์ - "การอ้างอิงที่ไม่ได้กำหนด / ข้อผิดพลาดของสัญลักษณ์ภายนอกที่ไม่ได้แก้ไข" เกิดขึ้นเมื่อตัวเชื่อมโยงไม่สามารถค้นหาสัญลักษณ์ทั่วโลกในไฟล์วัตถุ


71

สัญลักษณ์ถูกกำหนดในโปรแกรม C และใช้ในรหัส C ++

มีการกำหนดฟังก์ชัน (หรือตัวแปร) void foo()ในโปรแกรม C และคุณพยายามใช้ในโปรแกรม C ++:

void foo();
int main()
{
    foo();
}

ตัวเชื่อมโยง C ++ ต้องการชื่อที่ถูกเรนจ์ดังนั้นคุณต้องประกาศฟังก์ชันเป็น:

extern "C" void foo();
int main()
{
    foo();
}

เท่าเทียมกันแทนที่จะถูกกำหนดไว้ในโปรแกรม C ฟังก์ชั่น (หรือตัวแปร) void foo()ถูกกำหนดใน C ++ แต่ด้วยการเชื่อมโยง C:

extern "C" void foo();

และคุณพยายามใช้มันในโปรแกรม C ++ พร้อมลิงก์ C ++

หากไลบรารีทั้งหมดรวมอยู่ในไฟล์ส่วนหัว (และรวบรวมเป็นรหัส C) รวมถึงจะต้องมีดังนี้

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

5
หรือในทางกลับกันถ้าคุณพัฒนาไลบรารี C กฎที่ดีคือการปกป้องไฟล์ส่วนหัวโดยล้อมรอบการประกาศที่ส่งออกทั้งหมดด้วย#ifdef __cplusplus [\n] extern"C" { [\n] #endifและ#ifdef __cplusplus [\n] } [\n] #endif( [\n]เป็นสายการบินจริงคืน แต่ฉันไม่สามารถเขียนได้อย่างถูกต้องในความคิดเห็น)
Bentoy13

ในความคิดเห็นข้างต้นส่วน 'การสร้างส่วนหัวของภาษาผสม' ช่วยได้ที่นี่: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri

ช่วยฉันออกไปจริงๆ! นี่เป็นกรณีของฉันเมื่อฉันค้นหาคำถามนี้
knightofhorizon

นอกจากนี้ยังสามารถเกิดขึ้นได้ถ้าคุณรวม c ++ ไฟล์ส่วนหัวธรรมดาของคุณโดยอุบัติเหตุที่ล้อมรอบด้วยextern Cextern "C" { #include <myCppHeader.h> } :
ComFreek

68

หากสิ่งอื่นล้มเหลวให้คอมไพล์ใหม่

ฉันเพิ่งสามารถกำจัดข้อผิดพลาดภายนอกที่ยังไม่ได้แก้ไขใน Visual Studio 2012 เพียงแค่คอมไพล์ไฟล์ที่ละเมิดใหม่อีกครั้ง เมื่อฉันสร้างใหม่ข้อผิดพลาดก็หายไป

เรื่องนี้มักจะเกิดขึ้นเมื่อสอง (หรือมากกว่า) ห้องสมุดมีวงจรพึ่งพา Library A พยายามใช้สัญลักษณ์ใน B.lib และ Library B พยายามใช้สัญลักษณ์จาก A.lib ไม่มีทั้งที่จะเริ่มต้นด้วย เมื่อคุณพยายามรวบรวม A ขั้นตอนการเชื่อมโยงจะล้มเหลวเนื่องจากไม่พบ B.lib A.lib จะถูกสร้างขึ้น แต่ไม่มี dll จากนั้นคุณรวบรวม B ซึ่งจะประสบความสำเร็จและสร้าง B.lib การคอมไพล์ใหม่อีกครั้งจะทำงานได้เนื่องจากพบ B.lib


1
ถูกต้อง - สิ่งนี้เกิดขึ้นเมื่อไลบรารีมีการขึ้นต่อกันแบบวนซ้ำ
Luchian Grigore

1
ฉันขยายคำตอบของคุณและเชื่อมโยงในหลัก ขอบคุณ
Luchian Grigore

57

การนำเข้า / ส่งออกวิธี / คลาสอย่างไม่ถูกต้องทั่วโมดูล / dll (เฉพาะคอมไพเลอร์)

MSVS กำหนดให้คุณต้องระบุสัญลักษณ์ที่จะส่งออกและนำเข้าโดยใช้__declspec(dllexport)และ__declspec(dllimport)และ

การทำงานแบบคู่นี้มักจะได้รับจากการใช้มาโคร:

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

แมโครTHIS_MODULEจะถูกกำหนดในโมดูลที่ส่งออกฟังก์ชันเท่านั้น ด้วยวิธีนี้การประกาศ:

DLLIMPEXP void foo();

ขยายเป็น

__declspec(dllexport) void foo();

และบอกคอมไพเลอร์เพื่อส่งออกฟังก์ชั่นเป็นโมดูลปัจจุบันมีความหมายของมัน เมื่อรวมถึงการประกาศในโมดูลอื่นมันจะขยายไป

__declspec(dllimport) void foo();

และบอกคอมไพเลอร์ว่าคำจำกัดความเป็นหนึ่งในไลบรารีที่คุณเชื่อมโยงกับ (ดูเพิ่มเติมที่1) )

คุณสามารถนำเข้า / ส่งออกคลาสที่คล้ายคลึงกัน:

class DLLIMPEXP X
{
};

2
คำตอบนี้ควรกล่าวถึงไฟล์ของ GCC visibilityและ Windows .defเนื่องจากสิ่งเหล่านี้จะมีผลต่อชื่อสัญลักษณ์และสถานะ
rubenvb

@rubenvb ฉันไม่ได้ใช้.defไฟล์เป็นเวลานาน อย่าลังเลที่จะเพิ่มคำตอบหรือแก้ไขคำตอบนี้
Luchian Grigore

57

นี่เป็นหนึ่งในข้อความแสดงข้อผิดพลาดที่สับสนที่สุดที่โปรแกรมเมอร์ VC ++ ทุกคนได้เห็นครั้งแล้วครั้งเล่า มาทำความกระจ่างก่อนกัน

A. สัญลักษณ์คืออะไร? ในระยะสั้นสัญลักษณ์คือชื่อ มันอาจเป็นชื่อตัวแปรชื่อฟังก์ชั่นชื่อชั้นชื่อ typedef หรืออะไรก็ได้ยกเว้นชื่อและเครื่องหมายที่เป็นของภาษา C ++ มันเป็นผู้ใช้ที่กำหนดหรือแนะนำโดยห้องสมุดอ้างอิง (อื่นที่ผู้ใช้กำหนด)

B. ภายนอกคืออะไร ใน VC ++ ทุกไฟล์ต้นฉบับ (.cpp, .c, ฯลฯ ) ถือเป็นหน่วยการแปลคอมไพเลอร์รวบรวมหนึ่งหน่วยในแต่ละครั้งและสร้างหนึ่งวัตถุไฟล์ (.obj) สำหรับหน่วยการแปลปัจจุบัน (โปรดทราบว่าไฟล์ส่วนหัวทุกไฟล์ที่ไฟล์ต้นฉบับนี้รวมอยู่จะได้รับการประมวลผลล่วงหน้าและจะถือเป็นส่วนหนึ่งของหน่วยการแปลนี้) ทุกอย่างภายในหน่วยการแปลจะถูกพิจารณาว่าเป็นภายใน ใน C ++ คุณอาจอ้างอิงสัญลักษณ์ภายนอกโดยใช้คำหลักเช่นextern,__declspec (dllimport)และอื่น ๆ

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

  • ไฟล์วัตถุทั้งหมดที่สร้างขึ้นในเวลารวบรวม
  • ไลบรารีทั้งหมด (.lib) ที่ระบุอย่างชัดเจนหรือโดยนัยเป็นการอ้างอิงเพิ่มเติมของแอปพลิเคชันอาคารนี้

กระบวนการค้นหานี้เรียกว่าการแก้ไข

D. ในที่สุดทำไมสัญลักษณ์ภายนอกที่ไม่ได้รับการแก้ไข หากตัวลิงก์ไม่สามารถค้นหาคำจำกัดความภายนอกสำหรับสัญลักษณ์ที่ไม่มีคำจำกัดความภายในก็จะรายงานข้อผิดพลาดสัญลักษณ์ภายนอกที่ไม่ได้รับการแก้ไข

E. สาเหตุที่เป็นไปได้ของ LNK2019 : ข้อผิดพลาดสัญลักษณ์ภายนอกที่ไม่ได้รับการแก้ไข เรารู้อยู่แล้วว่าข้อผิดพลาดนี้เกิดจากตัวเชื่อมโยงล้มเหลวในการค้นหาคำจำกัดความของสัญลักษณ์ภายนอกสาเหตุที่เป็นไปได้สามารถจัดเรียงเป็น:

  1. คำจำกัดความที่มีอยู่

ตัวอย่างเช่นถ้าเรามีฟังก์ชั่นชื่อ foo ที่กำหนดใน a.cpp:

int foo()
{
    return 0;
}

ใน b.cpp เราต้องการเรียกฟังก์ชั่น foo ดังนั้นเราจึงเพิ่ม

void foo();

เพื่อประกาศฟังก์ชั่น foo () และเรียกมันในเนื้อหาฟังก์ชันอื่นพูดbar():

void bar()
{
    foo();
}

ตอนนี้เมื่อคุณสร้างรหัสนี้คุณจะได้รับข้อผิดพลาด LNK2019 ที่บ่นว่า foo เป็นสัญลักษณ์ที่ไม่ได้รับการแก้ไข ในกรณีนี้เรารู้ว่า foo () มีคำจำกัดความใน a.cpp แต่แตกต่างจากที่เราเรียก (ค่าตอบแทนต่างกัน) นี่เป็นกรณีที่คำจำกัดความมีอยู่

  1. ไม่มีคำจำกัดความ

หากเราต้องการเรียกบางฟังก์ชั่นในไลบรารี แต่ไลบรารีนำเข้าจะไม่ถูกเพิ่มเข้าไปในรายการการพึ่งพาเพิ่มเติม (ตั้งค่าจากProject | Properties | Configuration Properties | Linker | Input | Additional Dependency:) ของการตั้งค่าโครงการของคุณ ตอนนี้ตัวเชื่อมโยงจะรายงาน LNK2019 เนื่องจากคำจำกัดความไม่มีอยู่ในขอบเขตการค้นหาปัจจุบัน


1
ขอบคุณสำหรับคำอธิบายอย่างเป็นระบบ ฉันสามารถเข้าใจคำตอบอื่น ๆ ที่นี่หลังจากอ่านคำตอบนี้
displayName

55

การใช้งานเทมเพลตไม่สามารถมองเห็นได้

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

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

ในการแก้ไขปัญหานี้คุณต้องย้ายคำจำกัดความของ X::fooไปยังไฟล์ส่วนหัวหรือสถานที่ที่หน่วยการแปลที่ใช้งานได้ปรากฏให้เห็น

เทมเพลตเฉพาะทางสามารถนำไปใช้ในไฟล์การนำไปใช้และไม่ต้องมองเห็นการใช้งาน แต่ต้องประกาศความเชี่ยวชาญก่อนหน้านี้

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


1
ข้อมูลเพิ่มเติมสำหรับ "เทมเพลตจะต้องกำหนดในส่วนหัว" สามารถพบได้ในstackoverflow.com/questions/495021
PlasmaHH

41

การอ้างอิงที่ไม่ได้กำหนดWinMain@16หรือการอ้างอิงจุดเข้าที่ผิดปกติ main() (โดยเฉพาะสำหรับ)

คุณอาจไม่ได้เลือกประเภทโครงการที่เหมาะสมกับ IDE ที่แท้จริงของคุณ IDE อาจต้องการผูกเช่นโครงการ Windows Application กับฟังก์ชั่นจุดเข้าใช้งานดังกล่าว (ตามที่ระบุในการอ้างอิงที่ขาดหายไปด้านบน) แทนint main(int argc, char** argv);ลายเซ็นที่ใช้กันทั่วไป

หาก IDE ของคุณสนับสนุนโครงการคอนโซลธรรมดาคุณอาจต้องการเลือกประเภทโครงการนี้แทนโครงการแอปพลิเคชัน windows


ต่อไปนี้เป็นcase1และcase2 ที่จัดการในรายละเอียดเพิ่มเติมจากปัญหาโลกแห่งความจริง


2
อดไม่ได้ที่จะชี้ให้เห็นคำถามนี้และความจริงที่ว่านี่มักจะเกิดจากการที่ไม่มีหน้าที่หลักWinMainเลย ที่ถูกต้อง c ++ mainโปรแกรมต้อง
chris

36

นอกจากนี้หากคุณใช้ห้องสมุดบุคคลที่สามให้แน่ใจว่าคุณมีไบนารี 32/64 บิตที่ถูกต้อง


34

Microsoft เสนอ a #pragmaเพื่ออ้างอิงไลบรารีที่ถูกต้อง ณ เวลาลิงก์

#pragma comment(lib, "libname.lib")

นอกเหนือจากพา ธ ไลบรารีรวมถึงไดเร็กทอรีของไลบรารีนี่ควรเป็นชื่อเต็มของไลบรารี


34

แพคเกจ Visual Studio NuGet จำเป็นต้องได้รับการอัพเดตสำหรับชุดเครื่องมือรุ่นใหม่

ฉันเพิ่งมีปัญหานี้พยายามเชื่อมโยง libpng กับ Visual Studio 2013 ปัญหาคือไฟล์แพคเกจมีเฉพาะไลบรารีสำหรับ Visual Studio 2010 และ 2012

ทางออกที่ถูกต้องคือหวังว่านักพัฒนาซอฟต์แวร์จะปล่อยแพ็คเกจที่อัปเดตแล้วอัปเกรด แต่มันใช้งานได้โดยแฮ็คในการตั้งค่าพิเศษสำหรับ VS2013 โดยชี้ไปที่ไฟล์ไลบรารี VS2012

ฉันแก้ไขแพคเกจ (ในpackagesโฟลเดอร์ภายในไดเรกทอรีของโซลูชัน) โดยการค้นหาpackagename\build\native\packagename.targetsและภายในไฟล์นั้นคัดลอกv110ส่วนทั้งหมด ผมเปลี่ยนv110ไปv120ในทุ่งนาสภาพเพียงv110การระมัดระวังมากที่จะออกจากเส้นทางชื่อไฟล์ทั้งหมดเป็น สิ่งนี้ทำให้ Visual Studio 2013 สามารถเชื่อมโยงไปยังไลบรารีสำหรับปี 2012 และในกรณีนี้มันใช้งานได้


1
ดูเหมือนจะเฉพาะเจาะจงมากเกินไป - บางทีกระทู้ใหม่อาจเป็นสถานที่ที่ดีกว่าสำหรับคำตอบนี้
Luchian Grigore

3
@ LuchianGrigore: ฉันต้องการโพสต์ที่นี่เนื่องจากคำถามนั้นเป็นปัญหานี้ แต่มันถูกทำเครื่องหมายว่าซ้ำกับคำถามนี้ดังนั้นฉันจึงไม่สามารถตอบได้ที่นั่น ดังนั้นฉันโพสต์คำตอบของฉันที่นี่แทน
Malvineous

คำถามนั้นมีคำตอบที่ยอมรับแล้ว มันถูกทำเครื่องหมายว่าซ้ำเนื่องจากสาเหตุทั่วไประบุไว้ข้างต้น จะเกิดอะไรขึ้นถ้าเรามีคำตอบที่นี่สำหรับห้องสมุดที่ไม่รวม
Luchian Grigore

6
@LuchianGrigore: ปัญหานี้ไม่เฉพาะเจาะจงกับไลบรารี แต่จะมีผลกับไลบรารีทั้งหมดที่ใช้ระบบการจัดการแพ็คเกจของ Visual Studio ฉันเพิ่งพบคำถามอื่นเพราะเราทั้งคู่มีปัญหากับ libpng ฉันยังมีปัญหาเดียวกัน (ด้วยโซลูชันเดียวกัน) สำหรับ libxml2, libiconv และ glew คำถามนั้นเกี่ยวกับปัญหากับระบบการจัดการแพคเกจของ Visual Studio และคำตอบของฉันอธิบายเหตุผลและวิธีแก้ปัญหา บางคนเพิ่งเห็น "ภายนอกที่ไม่ได้รับการแก้ไข" และถือว่าเป็นปัญหาตัวเชื่อมโยงมาตรฐานเมื่อเป็นปัญหาการจัดการแพคเกจ
Malvineous

34

สมมติว่าคุณมีโปรเจ็กต์ใหญ่เขียนด้วย c ++ ซึ่งมีไฟล์. cpp หลายพันไฟล์และไฟล์. h ไฟล์หนึ่งพันและสมมุติว่าโปรเจ็กต์นั้นขึ้นอยู่กับไลบรารีแบบคงที่สิบตัว สมมติว่าเราอยู่บน Windows และเราสร้างโครงการของเราใน Visual Studio 20xx เมื่อคุณกด Ctrl + F7 Visual Studio เพื่อเริ่มรวบรวมโซลูชั่นทั้งหมด (สมมติว่าเรามีเพียงหนึ่งโครงการในโซลูชัน)

ความหมายของการรวบรวมคืออะไร?

  • Visual Studio ค้นหาเป็นไฟล์. vcxprojและเริ่มรวบรวมไฟล์แต่ละไฟล์ที่มีนามสกุล. cpp ลำดับการรวบรวมไม่ได้กำหนดดังนั้นคุณต้องไม่คิดว่าไฟล์ main.cpp จะถูกคอมไพล์ก่อน
  • หากไฟล์. cpp ขึ้นอยู่กับไฟล์. h เพิ่มเติมเพื่อค้นหาสัญลักษณ์ที่อาจกำหนดหรืออาจไม่ได้กำหนดไว้ในไฟล์. cpp
  • หากมีไฟล์. cpp อยู่หนึ่งไฟล์ซึ่งคอมไพเลอร์ไม่สามารถหาหนึ่งสัญลักษณ์ข้อผิดพลาดเกี่ยวกับเวลาของคอมไพเลอร์จะทำให้ข้อความSymbol x ไม่พบ
  • สำหรับแต่ละไฟล์ที่มีนามสกุล. cpp จะสร้างไฟล์วัตถุ. o และ Visual Studio จะเขียนผลลัพธ์ในไฟล์ชื่อProjectName.Cpp.Clean.txtซึ่งมีไฟล์วัตถุทั้งหมดที่ต้องดำเนินการโดยตัวเชื่อมโยง

ขั้นตอนที่สองของการรวบรวมทำโดย Linker.Linker ควรผสานวัตถุไฟล์ทั้งหมดและสร้างในที่สุดผลลัพธ์ (ซึ่งอาจจะปฏิบัติการหรือห้องสมุด)

ขั้นตอนในการเชื่อมโยงโครงการ

  • แยกไฟล์อ็อบเจ็กต์ทั้งหมดและค้นหาคำจำกัดความที่ถูกประกาศในส่วนหัวเท่านั้น (เช่น: โค้ดของวิธีหนึ่งของคลาสตามที่กล่าวไว้ในคำตอบก่อนหน้าหรือเหตุการณ์การเริ่มต้นของตัวแปรสแตติกซึ่งเป็นสมาชิกภายในคลาส)
  • หากไม่พบสัญลักษณ์หนึ่งในไฟล์วัตถุเขาจะค้นหาใน Libraries เพิ่มเติมสำหรับการเพิ่มไลบรารี่ใหม่ไปยังโปรเจ็กต์Properties Properties -> VC ++ Directories -> ไลบรารี่ไดเรกทอรีและที่นี่คุณระบุโฟลเดอร์เพิ่มเติมสำหรับการค้นหาไลบรารี่และคุณสมบัติ Configuration -> Linker -> อินพุตสำหรับระบุชื่อของไลบรารี - หาก Linker ไม่สามารถหาสัญลักษณ์ที่คุณเขียนในหนึ่ง. cpp เขายกข้อผิดพลาดเวลา linkerซึ่งอาจดูเหมือน error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

การสังเกต

  1. เมื่อ Linker พบสัญลักษณ์หนึ่งเขาจะไม่ค้นหาในห้องสมุดอื่นเพื่อหาสัญลักษณ์
  2. คำสั่งของห้องสมุดเชื่อมโยงไม่ก็ตาม
  3. หาก Linker พบสัญลักษณ์ภายนอกในห้องสมุดคงหนึ่งเขารวมถึงสัญลักษณ์ในการส่งออกของ project.However ถ้าห้องสมุดที่ใช้ร่วมกัน (ไดนามิก) เขาไม่ได้รวมไปถึงรหัส (สัญลักษณ์) ในการส่งออก แต่ เวลาเรียกใช้เกิดปัญหาอาจ เกิดขึ้น

วิธีแก้ไขข้อผิดพลาดประเภทนี้

ข้อผิดพลาดเกี่ยวกับเวลาของคอมไพเลอร์:

  • ตรวจสอบให้แน่ใจว่าคุณเขียนโครงร่างโครงการ c ++ ถูกต้อง

ข้อผิดพลาดเวลาของ Linker

  • กำหนดสัญลักษณ์ทั้งหมดที่คุณประกาศในไฟล์ส่วนหัวของคุณ
  • ใช้#pragma onceสำหรับการอนุญาตให้คอมไพเลอร์ไม่รวมหนึ่งส่วนหัวถ้ามันรวมอยู่ใน. cpp ปัจจุบันซึ่งถูกคอมไพล์แล้ว
  • ตรวจสอบให้แน่ใจว่าไลบรารีภายนอกของคุณไม่มีสัญลักษณ์ที่อาจมีความขัดแย้งกับสัญลักษณ์อื่น ๆ ที่คุณกำหนดไว้ในไฟล์ส่วนหัวของคุณ
  • เมื่อคุณใช้เทมเพลตเพื่อให้แน่ใจว่าคุณมีคำจำกัดความของแต่ละฟังก์ชั่นเทมเพลตในไฟล์ส่วนหัวเพื่อให้คอมไพเลอร์สามารถสร้างรหัสที่เหมาะสมสำหรับการสร้างอินสแตนซ์ได้

คำตอบของคุณไม่เฉพาะเจาะจงกับ Visual Studio หรือไม่? คำถามไม่ได้ระบุเครื่องมือ IDE / คอมไพเลอร์ใด ๆ ดังนั้นจึงทำให้คำตอบของคุณไร้ประโยชน์สำหรับชิ้นส่วนที่ไม่ใช่ภาพและสตูดิโอ
Victor Polevoy

คุณถูก . แต่ทุกกระบวนการ IDE ของการรวบรวม / การเชื่อมโยงจะทำแตกต่างกันเล็กน้อย แต่ไฟล์จะถูกประมวลผลเหมือนกัน (แม้ g ++ จะทำสิ่งเดียวกันเมื่อแยกวิเคราะห์แฟล็ก .. )

ปัญหาไม่ได้เกี่ยวกับ IDE แต่เป็นคำตอบสำหรับการเชื่อมโยงปัญหา ปัญหาการเชื่อมโยงไม่เกี่ยวข้องกับ IDE แต่รวมถึงกระบวนการคอมไพเลอร์และบิลด์
Victor Polevoy

ใช่ แต่กระบวนการสร้าง / การเชื่อมโยงกำลังดำเนินการใน g ++ / Visual Studio (คอมไพเลอร์ที่จัดทำโดย Microsoft สำหรับ VS) / Eclipse / Net Beans ด้วยวิธีเดียวกัน

29

ข้อบกพร่องในคอมไพเลอร์ / IDE

ฉันเพิ่งมีปัญหานี้และจะเปิดออกมันเป็นข้อผิดพลาดใน Visual Studio 2013 ฉันต้องลบไฟล์ต้นฉบับออกจากโครงการและเพิ่มใหม่อีกครั้งเพื่อแก้ไขข้อผิดพลาด

ขั้นตอนในการลองหากคุณเชื่อว่าอาจเป็นข้อผิดพลาดในคอมไพเลอร์ / IDE:

  • ทำความสะอาดโครงการ (IDE บางตัวมีตัวเลือกในการทำเช่นนี้คุณสามารถทำได้ด้วยตนเองโดยการลบไฟล์วัตถุ)
  • ลองเริ่มโครงการใหม่คัดลอกซอร์สโค้ดทั้งหมดจากต้นฉบับ

5
การเชื่อว่าเครื่องมือของคุณเสียหายเป็นไปได้มากที่สุดที่จะพาคุณออกไปจากสาเหตุที่แท้จริง เป็นไปได้มากว่าคุณทำผิดพลาดมากกว่าคอมไพเลอร์ทำให้เกิดปัญหาของคุณ การทำความสะอาดโซลูชันของคุณหรือสร้างการกำหนดค่าการสร้างใหม่อาจแก้ไขข้อผิดพลาดในการสร้าง แต่นั่นไม่ได้หมายความว่ามีข้อผิดพลาดในคอมไพเลอร์ ลิงก์ "เปิดออกแล้วเป็นข้อผิดพลาด" ไม่ได้รับการยืนยันจาก Microsoft และไม่สามารถทำซ้ำได้
JDiMatteo

4
@JDiMatteo มีคำตอบ 21 ข้อสำหรับคำถามนี้ดังนั้นคำตอบจำนวนมากไม่น่าจะเป็นคำตอบที่ "น่าจะ" หากคุณละทิ้งคำตอบทั้งหมดที่อยู่ต่ำกว่าเกณฑ์ความน่าจะเป็นของคุณหน้าเว็บนี้จะไร้ประโยชน์เพราะกรณีทั่วไปส่วนใหญ่จะพบเห็นได้ง่าย
developerbmw

27

ใช้ linker เพื่อช่วยวินิจฉัยข้อผิดพลาด

ตัวเชื่อมโยงที่ทันสมัยส่วนใหญ่มีตัวเลือก verbose ที่พิมพ์ออกมาเป็นองศาที่แตกต่างกัน

  • ลิงค์ภาวนา (บรรทัดคำสั่ง)
  • ข้อมูลเกี่ยวกับห้องสมุดที่จะรวมอยู่ในขั้นตอนการเชื่อมโยง
  • ที่ตั้งของห้องสมุด
  • ใช้พา ธ การค้นหา

สำหรับ gcc และ clang; โดยปกติแล้วคุณจะเพิ่ม-v -Wl,--verboseหรือ-v -Wl,-vลงในบรรทัดคำสั่ง รายละเอียดเพิ่มเติมสามารถดูได้ที่นี่;

  • ลินุกซ์หน้าคน LD
  • LLVM หน้าลิงเกอร์
  • "แนะนำให้รู้จักกับ GCC" บทที่ 9

สำหรับ MSVC, /VERBOSE(โดยเฉพาะอย่างยิ่งใน/VERBOSE:LIB) จะถูกเพิ่มในคำสั่งลิงค์

  • หน้า MSDN ในตัวเลือกลิงเกอร์/VERBOSE

26

ไฟล์. lib ที่เชื่อมโยงนั้นเชื่อมโยงกับ. dll

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

เพื่อแก้ปัญหานี้ฉันสร้าง MyProject เป็น LIB และเชื่อมโยง TestProject กับไฟล์. lib นี้ (ฉันคัดลอกวางไฟล์. lib ที่สร้างขึ้นลงในโฟลเดอร์ TestProject) ฉันสามารถสร้าง MyProject อีกครั้งในฐานะ DLL มันจะรวบรวมตั้งแต่ lib ที่ TestProject เชื่อมโยงจะมีรหัสสำหรับวิธีการทั้งหมดในชั้นเรียนใน MyProject


25

เนื่องจากคนดูเหมือนจะถูกนำไปยังคำถามนี้เมื่อมันมาถึงข้อผิดพลาด linker ฉันจะเพิ่มที่นี่

เหตุผลหนึ่งที่เป็นไปได้สำหรับข้อผิดพลาด linker กับ GCC 5.2.0 คือไลบรารี libstdc ++ ใหม่ ABI ถูกเลือกโดยค่าเริ่มต้น

หากคุณได้รับข้อผิดพลาด linker เกี่ยวกับการอ้างอิงที่ไม่ได้กำหนดสัญลักษณ์ที่เกี่ยวข้องกับประเภทในเนมสเปซ std :: __ cxx11 หรือแท็ก [abi: cxx11] ก็อาจบ่งบอกว่าคุณกำลังพยายามเชื่อมโยงไฟล์วัตถุต่างๆที่คอมไพล์ด้วย แมโคร สิ่งนี้มักเกิดขึ้นเมื่อเชื่อมโยงไปยังห้องสมุดบุคคลที่สามที่รวบรวมกับ GCC รุ่นเก่ากว่า หากห้องสมุดของบุคคลที่สามไม่สามารถสร้างขึ้นมาใหม่ด้วย ABI ใหม่คุณจะต้องคอมไพล์รหัสของคุณด้วย ABI เก่า

ดังนั้นหากคุณได้รับข้อผิดพลาด linker ทันทีเมื่อเปลี่ยนเป็น GCC หลังจาก 5.1.0 สิ่งนี้จะเป็นสิ่งที่ควรตรวจสอบ


20

wrapper รอบ ๆ GNU ld ที่ไม่รองรับสคริปต์ linker

ไฟล์. so บางไฟล์เป็นGNU ld linker scriptเช่นไฟล์libtbb.soเป็นไฟล์ข้อความ ASCII ที่มีเนื้อหานี้:

INPUT (libtbb.so.2)

งานสร้างที่ซับซ้อนบางอย่างอาจไม่รองรับสิ่งนี้ ตัวอย่างเช่นหากคุณรวม -v ไว้ในตัวเลือกคอมไพเลอร์คุณจะเห็นว่าmainwin gcc wrapper mwdipละทิ้งไฟล์คำสั่งสคริปต์ของ linker ในรายการเอาต์พุต verbose ของไลบรารีที่จะเชื่อมโยงการทำงานง่าย ๆ คือการแทนที่คำสั่ง linker script input ไฟล์ที่มีสำเนาของไฟล์แทน (หรือ symlink) เช่น

cp libtbb.so.2 libtbb.so

หรือคุณสามารถแทนที่อาร์กิวเมนต์ -l ด้วยพา ธ แบบเต็มของ. so เช่นแทนที่จะ-ltbbทำ/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


20

การเชื่อมโยงของคุณใช้ไลบรารีก่อนที่วัตถุไฟล์ที่อ้างถึง

  • คุณกำลังพยายามรวบรวมและเชื่อมโยงโปรแกรมของคุณกับ toolchain ของ GCC
  • ลิงก์ของคุณระบุไลบรารีและเส้นทางการค้นหาไลบรารีที่จำเป็นทั้งหมด
  • หากlibfooขึ้นอยู่กับlibbarแล้วการเชื่อมโยงของคุณอย่างถูกต้องทำให้ก่อนlibfoolibbar
  • การเชื่อมโยงของคุณล้มเหลวโดยมีข้อผิดพลาดundefined reference to บางอย่าง
  • แต่ทั้งหมดไม่ได้กำหนด บางสิ่งบางอย่างที่มีการประกาศในส่วนหัวของไฟล์ที่คุณมี #includeวันและในความเป็นจริงที่กำหนดไว้ในไลบรารีที่คุณมีการเชื่อมโยง

ตัวอย่างมีหน่วยเป็น C พวกเขาน่าจะเท่ากับ C ++

ตัวอย่างเล็กน้อยที่เกี่ยวข้องกับไลบรารีแบบคงที่ที่คุณสร้างขึ้นเอง

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

คุณสร้างไลบรารีแบบคงที่ของคุณ:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

คุณรวบรวมโปรแกรมของคุณ:

$ gcc -I. -c -o eg1.o eg1.c

คุณพยายามเชื่อมโยงกับlibmy_lib.aและล้มเหลว:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

ผลลัพธ์เดียวกันหากคุณรวบรวมและเชื่อมโยงในขั้นตอนเดียวเช่น:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

ตัวอย่างเล็กน้อยที่เกี่ยวข้องกับไลบรารีระบบที่ใช้ร่วมกันคือไลบรารีการบีบอัด libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

รวบรวมโปรแกรมของคุณ:

$ gcc -c -o eg2.o eg2.c

ลองลิงค์โปรแกรมของคุณด้วยlibzและล้มเหลว:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

เช่นเดียวกันถ้าคุณรวบรวมและลิงก์ในครั้งเดียว:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

และรูปแบบที่เกี่ยวข้องกับตัวอย่างที่ 2 pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

คุณทำอะไรผิด

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

ลิงก์ตัวอย่าง 1 อย่างถูกต้อง:

$ gcc -o eg1 eg1.o -L. -lmy_lib

ความสำเร็จ:

$ ./eg1 
Hello World

ลิงก์ตัวอย่าง 2 อย่างถูกต้อง:

$ gcc -o eg2 eg2.o -lz

ความสำเร็จ:

$ ./eg2 
1.2.8

เชื่อมโยงตัวอย่างpkg-configรูปแบบ2 อย่างถูกต้อง:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

คำอธิบาย

การอ่านเป็นตัวเลือกจากที่นี่

ตามค่าเริ่มต้นคำสั่ง linkage ที่สร้างโดย GCC บน distro ของคุณจะใช้ไฟล์ในการลิงก์จากซ้ายไปขวาในลำดับ commandline เมื่อพบว่าไฟล์อ้างถึงบางสิ่งบางอย่าง และไม่มีคำนิยามสำหรับไฟล์นั้นเพื่อค้นหาคำจำกัดความในไฟล์ที่อยู่ทางด้านขวา หากในที่สุดพบคำจำกัดความการอ้างอิงจะได้รับการแก้ไข หากการอ้างอิงใด ๆ ยังคงไม่ได้รับการแก้ไขในตอนท้ายการเชื่อมโยงล้มเหลว: ตัวเชื่อมโยงจะไม่ค้นหาข้อมูลย้อนกลับ

ครั้งแรกตัวอย่างที่ 1พร้อมกับห้องสมุดแบบคงที่my_lib.a

ไลบรารีแบบสแตติกเป็นไฟล์เก็บถาวรที่จัดทำดัชนีของไฟล์วัตถุ เมื่อตัวเชื่อมโยงพบ-lmy_libในลำดับการเชื่อมโยงและหาว่านี่หมายถึงไลบรารีแบบคงที่./libmy_lib.aมันต้องการทราบว่าโปรแกรมของคุณต้องการไฟล์วัตถุใด ๆ หรือlibmy_lib.aไม่

มีไฟล์วัตถุเพียงอย่างเดียวคือในlibmy_lib.aคือmy_lib.oและมีเพียงสิ่งเดียวที่กำหนดไว้ในคือฟังก์ชั่นmy_lib.ohw

ตัวเชื่อมโยงจะตัดสินใจว่าโปรแกรมของคุณต้องการmy_lib.oหากว่ามันรู้อยู่แล้วว่าโปรแกรมของคุณอ้างถึงhwในไฟล์ออบเจ็กต์หนึ่งไฟล์หรือมากกว่านั้นที่เพิ่มเข้าไปในโปรแกรมแล้วและไม่มีไฟล์ออบเจ็กต์ที่เพิ่มเข้าไปแล้ว hwคำนิยาม

หากเป็นจริงตัวลิงก์จะแยกสำเนาของmy_lib.oจากไลบรารีและเพิ่มลงในโปรแกรมของคุณ จากนั้นโปรแกรมของคุณมีความหมายสำหรับhwเพื่อการอ้างอิงในการhwจะได้รับการแก้ไข

เมื่อคุณพยายามเชื่อมโยงโปรแกรมเช่น:

$ gcc -o eg1 -L. -lmy_lib eg1.o

ลิงเกอร์ยังไม่ได้เพิ่ม eg1.o ไปยังโปรแกรม-lmy_libเมื่อเห็น eg1.oเพราะในจุดนั้นมันยังไม่เห็น โปรแกรมของคุณยังไม่ให้การอ้างอิงใด ๆhw: มันยังไม่ให้การอ้างอิงใด ๆที่ทุกคนeg1.oเพราะการอ้างอิงทั้งหมดมันทำให้อยู่ใน

ดังนั้นลิงเกอร์ไม่เพิ่มในการเขียนโปรแกรมและยังไม่มีการใช้งานต่อไปสำหรับmy_lib.olibmy_lib.a

ถัดไปพบeg1.oและเพิ่มเป็นโปรแกรม อ็อบเจ็กต์ไฟล์ในลำดับการลิงก์ถูกเพิ่มเข้ากับโปรแกรมเสมอ ตอนนี้โปรแกรมทำการอ้างอิงถึงhwและไม่มีคำจำกัดความของhw; แต่ไม่มีอะไรเหลืออยู่ในลำดับการเชื่อมโยงที่สามารถให้คำจำกัดความที่ขาดหายไป การอ้างอิงเพื่อhwสิ้นสุดยังไม่ได้แก้ไขและการเชื่อมโยงล้มเหลว

ประการที่สองตัวอย่างที่ 2พร้อมห้องสมุดสาธารณะlibz

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

หลาย Linux distros วันนี้กำหนดค่าของพวกเขา GCC toolchain เพื่อให้ไดรเวอร์ที่ภาษา ( gcc, g++, gfortranฯลฯ ) สั่งลิงเกอร์ระบบ ( ld) เพื่อเชื่อมโยงร่วมกันห้องสมุดในความจำเป็นพื้นฐาน คุณมีหนึ่งในความโกลาหลเหล่านั้น

ซึ่งหมายความว่าเมื่อตัวเชื่อมโยงพบ-lzในลำดับการเชื่อมโยงและคิดว่าสิ่งนี้อ้างอิงถึงไลบรารีที่ใช้ร่วมกัน (พูด) /usr/lib/x86_64-linux-gnu/libz.soก็ต้องการที่จะรู้ว่าการอ้างอิงใด ๆ ที่เพิ่มไว้ในโปรแกรมของคุณที่ยังไม่ได้กำหนดมีคำจำกัดความที่ ส่งออกโดยlibz

หากเป็นเช่นนั้นตัวลิงก์จะไม่คัดลอกชิ้นส่วนใด ๆ ออกจากlibzและเพิ่มลงในโปรแกรมของคุณ แต่จะเป็นหมอตรวจสอบรหัสของโปรแกรมของคุณเพื่อ: -

  • เมื่อรันไทม์ตัวโหลดโปรแกรมระบบจะโหลดสำเนาของlibzกระบวนการเดียวกันกับโปรแกรมของคุณเมื่อใดก็ตามที่มันโหลดสำเนาของโปรแกรมของคุณเพื่อเรียกใช้

  • ณ รันไทม์เมื่อใดก็ตามที่โปรแกรมของคุณอ้างถึงสิ่งที่กำหนดไว้ libzการอ้างอิงนั้นจะใช้คำจำกัดความที่ส่งออกโดยสำเนาของlibzกระบวนการเดียวกัน

โปรแกรมของคุณต้องการที่จะหมายถึงเพียงสิ่งหนึ่งที่มีความหมายส่งออกโดยlibzคือฟังก์ชั่นซึ่งจะเรียกเพียงครั้งเดียวในzlibVersion eg2.cหาก linker เพิ่มการอ้างอิงนั้นไปยังโปรแกรมของคุณแล้วค้นหาคำจำกัดความที่ส่งออกโดยlibzการอ้างอิงนั้นจะได้รับการแก้ไข

แต่เมื่อคุณพยายามเชื่อมโยงโปรแกรมเช่น:

gcc -o eg2 -lz eg2.o

ลำดับของเหตุการณ์ที่เกิดขึ้นเป็นความผิดในลักษณะเช่นเดียวกับกับตัวอย่างเช่น 1. ที่จุดเมื่อพบลิงเกอร์-lzมีไม่มีการอ้างอิงถึงสิ่งที่อยู่ในโปรแกรม: พวกเขาอยู่ในทั้งหมดeg2.oซึ่งยังไม่ได้รับการเห็น ดังนั้นลิงเกอร์ตัดสินใจว่ามันไม่มีประโยชน์libzอะไร เมื่อมันมาถึงeg2.oให้เพิ่มมันเข้าไปในโปรแกรมแล้วมีการอ้างอิงที่ไม่ได้กำหนดzlibVersionลำดับการเชื่อมโยงจะเสร็จสิ้น การอ้างอิงนั้นไม่ได้รับการแก้ไขและการเชื่อมโยงล้มเหลว

สุดท้ายการpkg-configเปลี่ยนแปลงของตัวอย่าง 2 มีคำอธิบายที่ชัดเจนในขณะนี้ หลังจากการขยายตัวของเชลล์:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

กลายเป็น:

gcc -o eg2 -lz eg2.o

ซึ่งเป็นเพียงตัวอย่าง 2 อีกครั้ง

ฉันสามารถทำให้เกิดปัญหาในตัวอย่างที่ 1 แต่ไม่ได้อยู่ในตัวอย่างที่ 2

การเชื่อมโยง:

gcc -o eg2 -lz eg2.o

ทำงานได้ดีสำหรับคุณ!

(หรือ: การเชื่อมโยงนั้นใช้งานได้ดีสำหรับคุณพูด Fedora 23 แต่ล้มเหลวบน Ubuntu 16.04)

นั่นเป็นเพราะ distro ที่เชื่อมโยงการทำงานเป็นหนึ่งในคนที่ไม่ได้กำหนดค่า toolchain GCC ในการเชื่อมโยงห้องสมุดสาธารณะความจำเป็น

ย้อนกลับไปในวันมันเป็นเรื่องปกติสำหรับระบบที่เหมือนยูนิกซ์ในการเชื่อมโยงไลบรารีแบบคงที่และที่ใช้ร่วมกันตามกฎต่างๆ ไลบรารีแบบสแตติกในลำดับการลิงก์ถูกลิงก์บนพื้นฐานตามที่ต้องการอธิบายไว้ในตัวอย่างที่ 1 แต่ไลบรารีแบบแบ่งใช้ถูกลิงก์แบบไม่มีเงื่อนไข

ลักษณะการทำงานนี้ประหยัดที่ linktime เนื่องจากตัวเชื่อมโยงไม่จำเป็นต้องไตร่ตรองว่าโปรแกรมที่ใช้ร่วมกับไลบรารีนั้นต้องการหรือไม่: ถ้าเป็นไลบรารีที่แชร์ให้ทำการเชื่อมโยง และห้องสมุดส่วนใหญ่ในการเชื่อมโยงส่วนใหญ่เป็นห้องสมุดสาธารณะ แต่ก็มีข้อเสียเช่นกัน: -

  • มันไม่ประหยัดที่รันไทม์เนื่องจากอาจทำให้ไลบรารีที่แชร์ถูกโหลดพร้อมกับโปรแกรมแม้ว่าจะไม่ต้องการก็ตาม

  • กฎการเชื่อมโยงที่แตกต่างกันสำหรับไลบรารีแบบคงที่และแบบแบ่งใช้อาจทำให้สับสนกับโปรแกรมเมอร์ที่ไม่มีประสบการณ์ซึ่งอาจไม่รู้ว่า-lfooในการเชื่อมโยงของพวกเขากำลังจะแก้ไข/some/where/libfoo.aหรือ/some/where/libfoo.soไม่และอาจไม่เข้าใจความแตกต่างระหว่างไลบรารีแบบแบ่งใช้และแบบคงที่

การแลกเปลี่ยนนี้นำไปสู่สถานการณ์ที่แตกแยกในทุกวันนี้ บาง distros ได้เปลี่ยนกฎการเชื่อมโยง GCC ของพวกเขาสำหรับไลบรารีที่ใช้ร่วมกันเพื่อที่ จะใช้หลักการที่จำเป็นสำหรับไลบรารีทั้งหมด บาง distros ติดอยู่กับวิธีเก่า

ทำไมฉันยังคงพบปัญหานี้แม้ว่าฉันจะรวบรวมและเชื่อมโยงในเวลาเดียวกัน?

ถ้าฉันทำ:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

แน่นอน GCC มีการรวบรวมแรกและจากนั้นเชื่อมโยงไฟล์วัตถุที่เกิดขึ้นกับeg1.c libmy_lib.aดังนั้นจะไม่ทราบได้อย่างไรว่าไฟล์อ็อบเจ็กต์นั้นต้องการเมื่อทำการลิงก์?

เพราะการรวบรวมและเชื่อมโยงกับคำสั่งเดียวไม่ได้เปลี่ยนลำดับของการเชื่อมโยงลำดับ

เมื่อคุณเรียกใช้คำสั่งข้างต้นgccตัวเลขที่คุณต้องการรวบรวม + เชื่อมโยง ดังนั้นเบื้องหลังมันสร้างคำสั่งการคอมไพล์และรันมันจากนั้นสร้างคำสั่งการเชื่อมโยงและรันมันราวกับว่าคุณได้รันทั้งสองคำสั่ง:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

ดังนั้นการเชื่อมต่อล้มเหลวเช่นเดียวกับที่มันไม่ถ้าคุณไม่เรียกใช้ทั้งสองคำสั่ง แตกต่างเพียงคุณสังเกตเห็นในความล้มเหลวคือการที่ GCC ได้สร้างไฟล์วัตถุชั่วคราวในกรณีที่รวบรวม + eg1.oการเชื่อมโยงเพราะคุณไม่ได้บอกให้ใช้ ที่เราเห็น:

/tmp/ccQk1tvs.o: In function `main'

แทน:

eg1.o: In function `main':

ดูสิ่งนี้ด้วย

ลำดับการระบุไลบรารีที่เชื่อมโยงซึ่งกันและกันซึ่งระบุนั้นผิด

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


18

แม่แบบตีสนิท ...

รับข้อมูลโค้ดของประเภทเทมเพลตที่มีโอเปอเรเตอร์เพื่อน (หรือฟังก์ชัน);

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

operator<<จะถูกประกาศเป็นฟังก์ชั่นที่ไม่ใช่แม่แบบ สำหรับทุกประเภทTใช้กับมีความต้องการที่จะเป็นไม่ใช่เทมเพลตFoo operator<<ตัวอย่างเช่นหากมีการFoo<int>ประกาศประเภทแล้วจะต้องมีการดำเนินการดำเนินการดังนี้

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

เนื่องจากไม่ได้ใช้งานตัวเชื่อมโยงจึงไม่สามารถค้นหาได้และทำให้เกิดข้อผิดพลาด

หากต้องการแก้ไขสิ่งนี้คุณสามารถประกาศโอเปอเรเตอร์เทมเพลตก่อนFooประเภทจากนั้นประกาศว่าเป็นเพื่อนการสร้างอินสแตนซ์ที่เหมาะสม ไวยากรณ์ค่อนข้างอึดอัดเล็กน้อย แต่มีลักษณะดังนี้

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

รหัสข้างต้น จำกัด มิตรภาพของผู้ประกอบการที่จะ instantiation ที่สอดคล้องกันของFooคือoperator<< <int>การเริ่มมีข้อ จำกัด ในการเข้าถึงสมาชิกส่วนตัวของ instantiation Foo<int>ของ

ทางเลือก ได้แก่ ;

  • การอนุญาตให้มิตรภาพขยายไปสู่อินสแตนซ์ทั้งหมดของเทมเพลตดังต่อไปนี้

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • หรือการใช้งานสำหรับoperator<<สามารถทำได้แบบอินไลน์ภายในคำจำกัดความของชั้นเรียน;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };

หมายเหตุเมื่อประกาศของผู้ประกอบการ (หรือฟังก์ชั่น) เท่านั้นที่ปรากฏในชั้นเรียนชื่อที่ไม่สามารถใช้ได้สำหรับ "ปกติ" ค้นหาเพียงเพื่อโต้แย้งการค้นหาขึ้นจากcppreference ;

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

มีการอ่านเพิ่มเติมเกี่ยวกับเพื่อนของแม่แบบที่เป็นcppreferenceและC ++ คำถามที่พบบ่อย

รหัสรายชื่อการแสดงเทคนิคดังกล่าวข้างต้น


ในฐานะที่เป็นบันทึกด้านข้างเพื่อตัวอย่างรหัสความล้มเหลว; g ++ เตือนเกี่ยวกับสิ่งนี้ดังนี้

warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)


16

เมื่อเส้นทางรวมของคุณแตกต่างกัน

ข้อผิดพลาดของ Linker สามารถเกิดขึ้นได้เมื่อไฟล์ส่วนหัวและไลบรารีที่แชร์ที่เกี่ยวข้อง (ไฟล์. lib) ขาดการซิงค์ ให้ฉันอธิบาย

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

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

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

โปรดสังเกตว่าแม้ว่าการประกาศฟังก์ชั่นทั้งสองจะมีลักษณะเหมือนกันในซอร์สโค้ด แต่มันก็แตกต่างกันไปตามคอมไพเลอร์

คุณอาจถามว่าจบลงในสถานการณ์เช่นนั้นได้อย่างไร รวมเส้นทางแน่นอน! หากเมื่อรวบรวมห้องสมุดสาธารณะเส้นทางรวมจะนำไปสู่header1.hและคุณใช้header2.hในโปรแกรมของคุณเองคุณจะถูกเกาหัวส่วนหัวของคุณสงสัยว่าเกิดอะไรขึ้น (ปุนตั้งใจ)

ตัวอย่างของสิ่งที่สามารถเกิดขึ้นได้ในโลกแห่งความเป็นจริงได้อธิบายไว้ด้านล่าง

รายละเอียดเพิ่มเติมด้วยตัวอย่าง

ฉันมีสองโครงการ: และgraphics.lib ทั้งสองโครงการขึ้นอยู่กับmain.exe common_math.hสมมติว่าไลบรารีส่งออกฟังก์ชันต่อไปนี้:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

จากนั้นคุณไปข้างหน้าและรวมไลบรารีไว้ในโครงการของคุณเอง

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

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

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

การดีบักตัวลิงก์

DUMPBIN เป็นเพื่อนของคุณถ้าคุณใช้ Visual Studio ฉันแน่ใจว่าคอมไพเลอร์รายอื่นมีเครื่องมืออื่นที่คล้ายคลึงกัน

กระบวนการจะเป็นดังนี้:

  1. สังเกตชื่อแปลก ๆ ที่ให้ไว้ในข้อผิดพลาดของ linker (เช่นวาด @ graphics @ XYZ)
  2. ดัมพ์สัญลักษณ์ที่เอ็กซ์พอร์ตจากไลบรารีลงในไฟล์ข้อความ
  3. ค้นหาสัญลักษณ์ที่น่าสนใจที่ส่งออกและสังเกตว่าชื่อ mangled นั้นแตกต่างกัน
  4. ให้ความสนใจกับสาเหตุที่ชื่อ mangled แตกต่างกันไป คุณจะสามารถเห็นได้ว่าประเภทพารามิเตอร์แตกต่างกันแม้ว่าพวกเขาจะดูเหมือนกันในซอร์สโค้ด
  5. เหตุผลที่แตกต่างกัน ในตัวอย่างที่ระบุข้างต้นพวกเขาแตกต่างกันเนื่องจากไฟล์รวมที่แตกต่างกัน

[1] ตามโครงการฉันหมายถึงชุดของไฟล์ต้นฉบับที่เชื่อมโยงเข้าด้วยกันเพื่อสร้างไลบรารีหรือไฟล์ที่เรียกใช้งานได้

แก้ไข 1: เขียนซ้ำส่วนแรกเพื่อให้เข้าใจง่ายขึ้น โปรดแสดงความคิดเห็นด้านล่างเพื่อแจ้งให้เราทราบหากจำเป็นต้องแก้ไขสิ่งอื่น ขอบคุณ!


15

UNICODEคำจำกัดความที่ไม่สอดคล้องกัน

ที่ใช้ Windows UNICODE สร้างถูกสร้างขึ้นด้วยTCHARฯลฯ ถูกกำหนดให้เป็นwchar_tฯลฯ เมื่อไม่ได้สร้างด้วยUNICODEการกำหนดเป็นสร้างที่มีTCHARกำหนดเป็นcharฯลฯ เหล่านี้UNICODEและ_UNICODEกำหนดส่งผลกระทบต่อทุกคน" T" ประเภทสตริง ; LPTSTR, LPCTSTRและกวางของพวกเขา

การสร้างหนึ่งไลบรารีที่มีการUNICODEกำหนดและพยายามลิงก์ในโครงการที่UNICODEไม่ได้กำหนดไว้จะส่งผลให้เกิดข้อผิดพลาดของ linker เนื่องจากจะมีความไม่ตรงกันในคำจำกัดความของTCHAR; charกับwchar_t.

ข้อผิดพลาดมักจะมีฟังก์ชั่นค่าที่มีcharหรือwchar_tประเภทที่ได้รับเหล่านี้อาจรวมถึงstd::basic_string<>ฯลฯ เช่นกัน เมื่อเรียกดูผ่านฟังก์ชั่นที่ได้รับผลกระทบในรหัสมักจะมีการอ้างอิงถึงTCHARหรือstd::basic_string<TCHAR>อื่น ๆ นี่คือสัญญาณบอกเล่าว่ารหัสเดิมมีไว้สำหรับทั้ง UNICODE และอักขระแบบหลายไบต์ (หรือ "แคบ") .

หากต้องการแก้ไขสิ่งนี้ให้สร้างไลบรารีและโครงการที่จำเป็นทั้งหมดด้วยคำจำกัดความที่สอดคล้องกันของUNICODE(และ_UNICODE)

  1. สามารถทำได้ด้วยเช่นกัน

    #define UNICODE
    #define _UNICODE
  2. หรือในการตั้งค่าโครงการ

    คุณสมบัติโครงการ> ทั่วไป> ค่าเริ่มต้นของโครงการ> ชุดอักขระ

  3. หรือบนบรรทัดคำสั่ง

    /DUNICODE /D_UNICODE

ทางเลือกสามารถใช้งานได้เช่นกันหากไม่ได้ใช้ UNICODE ตรวจสอบให้แน่ใจว่าไม่มีการตั้งค่าการกำหนดและ / หรือการตั้งค่าอักขระหลายตัวถูกใช้ในโครงการและนำไปใช้อย่างต่อเนื่อง

อย่าลืมว่าจะต้องสอดคล้องกันระหว่างบิลด์ "Release" และ "Debug" เช่นกัน


14

ทำความสะอาดและสร้างใหม่

"clean" ของบิลด์สามารถลบ "dead wood" ที่อาจทิ้งไว้จากการสร้างก่อนหน้าบิวด์ที่ล้มเหลวบิวด์ที่ไม่สมบูรณ์บิวด์ที่ไม่สมบูรณ์และบิลด์อื่น ๆ ที่เกี่ยวข้องกับบิลด์

โดยทั่วไปแล้ว IDE หรือบิลด์จะมีฟังก์ชั่น "clean" บางรูปแบบ แต่อาจไม่ได้รับการกำหนดค่าอย่างถูกต้อง (เช่นใน makefile แบบกำหนดเอง) หรืออาจล้มเหลว (เช่นไบนารีกลางหรือผลลัพธ์เป็นแบบอ่านอย่างเดียว)

เมื่อ "clean" เสร็จสิ้นให้ตรวจสอบว่า "clean" สำเร็จแล้วและไฟล์กลางทั้งหมดที่สร้างขึ้น (เช่น makefile อัตโนมัติ) ถูกลบออกเรียบร้อยแล้ว

นี้กระบวนการสามารถมองเห็นได้เป็นที่พึ่งสุดท้าย แต่มักจะเป็นก้าวแรกที่ดี ; โดยเฉพาะอย่างยิ่งถ้ารหัสที่เกี่ยวข้องกับข้อผิดพลาดได้รับการเพิ่มเมื่อเร็ว ๆ นี้ (ทั้งในพื้นที่หรือจากแหล่งเก็บข้อมูล)


10

ไม่มี "extern" ในconstการประกาศตัวแปร / คำจำกัดความ (C ++ เท่านั้น)

สำหรับผู้ที่มาจาก C อาจเป็นเรื่องน่าแปลกใจที่ในconstตัวแปรโกลบอล C ++ นั้นมีการเชื่อมโยงภายใน ใน C นี่ไม่ใช่กรณีเช่นเดียวกับตัวแปรโกลบอลทั้งหมดโดยปริยายextern(เช่นเมื่อstaticคำหลักหายไป)

ตัวอย่าง:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}

แก้ไขจะใช้ไฟล์ส่วนหัวและรวมไว้ใน file2.cpp และ file1.cpp

extern const int test;
extern int test2;

อีกทางเลือกหนึ่งสามารถประกาศconstตัวแปรใน file1.cpp อย่างชัดเจนextern


8

แม้ว่านี่จะเป็นคำถามเก่าที่มีคำตอบที่ยอมรับได้หลายข้อ แต่ฉันต้องการแบ่งปันวิธีแก้ไขข้อผิดพลาด "การอ้างอิงที่ไม่ได้กำหนดถึง" ที่ คลุมเครือ

ไลบรารีเวอร์ชันต่าง ๆ

ฉันใช้นามแฝงเพื่ออ้างถึงstd::filesystem::path: ระบบไฟล์อยู่ในไลบรารีมาตรฐานตั้งแต่ C ++ 17 แต่โปรแกรมของฉันจำเป็นต้องคอมไพล์ใน C ++ 14 ด้วยดังนั้นฉันตัดสินใจใช้ตัวแปรนามแฝง:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif

สมมติว่าฉันมีสามไฟล์: main.cpp, file.h, file.cpp:

  • file.h # include ของ < experiment :: ระบบแฟ้ม > และมีรหัสข้างต้น
  • file.cppการใช้งาน file.h, # include ของ " file.h "
  • main.cpp # include ของ < filesystem > และ " file.h "

หมายเหตุไลบรารีต่าง ๆ ที่ใช้ใน main.cpp และ file.h ตั้งแต่ main.cpp # include'd " file.h " หลังจาก < ระบบแฟ้ม > รุ่นของระบบแฟ้มที่ใช้มีc ++ 17 หนึ่ง ฉันเคยรวบรวมโปรแกรมด้วยคำสั่งต่อไปนี้:

$ g++ -g -std=c++17 -c main.cpp-> รวบรวม main.cpp ไปยัง main.o
$ g++ -g -std=c++17 -c file.cpp-> รวบรวม file.cpp และ file.h ไปยัง file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> ลิงก์ main.o และ file.o

วิธีนี้ฟังก์ชั่นใด ๆที่มีอยู่ใน file.o และใช้ใน main.o ที่ต้องpath_tให้ "การอ้างอิงที่ไม่ได้กำหนดข้อผิดพลาด" เพราะmain.oเรียกstd::filesystem::pathแต่file.ostd::experimental::filesystem::pathไป

มติ

ในการแก้ไขปัญหานี้ผมเพียงต้องการที่จะเปลี่ยนแปลง <ทดลอง :: ระบบแฟ้ม> ใน file.h ที่ <ระบบแฟ้ม>


5

เมื่อเชื่อมโยงกับไลบรารีที่ใช้ร่วมกันตรวจสอบให้แน่ใจว่าสัญลักษณ์ที่ใช้ไม่ถูกซ่อนอยู่

พฤติกรรมเริ่มต้นของ gcc คือสัญลักษณ์ทั้งหมดสามารถมองเห็นได้ อย่างไรก็ตามเมื่อหน่วยการแปลถูกสร้างขึ้นด้วยตัวเลือก-fvisibility=hiddenเฉพาะฟังก์ชั่น / สัญลักษณ์ที่ทำเครื่องหมายด้วย__attribute__ ((visibility ("default")))เป็นภายนอกในวัตถุที่ใช้ร่วมกันที่เกิดขึ้น

คุณสามารถตรวจสอบว่าสัญลักษณ์ที่คุณกำลังมองหาอยู่ภายนอกหรือไม่โดยเรียกใช้:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

สัญลักษณ์ซ่อน / โลคัลถูกแสดงnmด้วยชนิดสัญลักษณ์ตัวพิมพ์เล็กตัวอย่างเช่นtแทนที่จะเป็น `T สำหรับส่วนของรหัส:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

นอกจากนี้คุณยังสามารถใช้nmกับตัวเลือก-Cเพื่อแยกชื่อ (ถ้าใช้ C ++)

คล้ายกับ Windows-dll ที่หนึ่งจะทำเครื่องหมายฟังก์ชั่นสาธารณะด้วยการกำหนดตัวอย่างเช่นDLL_PUBLICกำหนดเป็น:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

ซึ่งสอดคล้องกับเวอร์ชั่น Windows / MSVC คร่าวๆ:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

เพิ่มเติมข้อมูลเกี่ยวกับการมองเห็นสามารถพบได้ในวิกิพีเดียจีซี


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

ในการค้นหาสัญลักษณ์ใดในไฟล์วัตถุที่ซ่อนอยู่ให้เรียกใช้

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

คุณควรใช้nm -CDหรือnm -gCDดูสัญลักษณ์ภายนอก ดูการมองเห็นบนวิกิ GCC ด้วย
jww

2

สถาปัตยกรรมที่แตกต่าง

คุณอาจเห็นข้อความเช่น:

library machine type 'x64' conflicts with target machine type 'X86'

ในกรณีดังกล่าวหมายความว่าสัญลักษณ์ที่มีสำหรับสถาปัตยกรรมที่แตกต่างจากที่คุณรวบรวม

บน Visual Studio นี่เป็นเพราะ "แพลตฟอร์ม" ผิดและคุณต้องเลือกอย่างใดอย่างหนึ่งที่เหมาะสมหรือติดตั้งรุ่นที่เหมาะสมของห้องสมุด

บน Linux อาจเป็นเพราะโฟลเดอร์ไลบรารีผิด (ใช้libแทนlib64ตัวอย่าง)

บน MacOS มีตัวเลือกในการจัดส่งสถาปัตยกรรมทั้งสองในไฟล์เดียวกัน อาจเป็นได้ว่าลิงก์คาดว่าทั้งสองเวอร์ชันจะอยู่ที่นั่น แต่มีเพียงเวอร์ชั่นเดียวเท่านั้น นอกจากนี้ยังสามารถเป็นปัญหากับการที่ไม่ถูกต้องlib/ lib64โฟลเดอร์ที่ห้องสมุดจะหยิบขึ้นมา

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