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


128

ฉันกำลังพยายามทำความเข้าใจว่าเกิดอะไรขึ้นเมื่อโมดูลที่มี globals และตัวแปรคงที่เชื่อมโยงกับแอปพลิเคชันแบบไดนามิก ตามโมดูลฉันหมายถึงแต่ละโครงการในโซลูชัน (ฉันทำงานกับสตูดิโอภาพเยอะมาก!) โมดูลเหล่านี้มีอยู่ในตัว * .lib หรือ * .dll หรือตัว * .exe เอง

ฉันเข้าใจว่าไบนารีของแอปพลิเคชันมีข้อมูลส่วนกลางและแบบคงที่ของหน่วยการแปลแต่ละหน่วย (ไฟล์ออบเจ็กต์) ในส่วนข้อมูล (และอ่านเฉพาะส่วนข้อมูลหาก const)

  • จะเกิดอะไรขึ้นเมื่อแอปพลิเคชันนี้ใช้โมดูล A ที่มีการเชื่อมโยงแบบไดนามิกในเวลาโหลด ฉันถือว่า DLL มีส่วนสำหรับโลกและสถิตยศาสตร์ ระบบปฏิบัติการโหลดหรือไม่ ถ้าเป็นเช่นนั้นจะโหลดไปที่ใด

  • และจะเกิดอะไรขึ้นเมื่อแอปพลิเคชันใช้โมดูล B พร้อมการเชื่อมโยงแบบไดนามิกรันไทม์

  • หากฉันมีโมดูลสองโมดูลในแอปพลิเคชันของฉันที่ทั้งสองใช้ A และ B สำเนาของโลกของ A และ B จะถูกสร้างขึ้นตามที่ระบุไว้ด้านล่าง (หากเป็นกระบวนการที่แตกต่างกัน)

  • DLLs A และ B สามารถเข้าถึงแอปพลิเคชัน globals ได้หรือไม่

(โปรดระบุเหตุผลของคุณด้วย)

อ้างจากMSDN :

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

และจากที่นี่ :

เมื่อเชื่อมโยงโมดูลแบบไดนามิกอาจไม่ชัดเจนว่าไลบรารีต่างๆมีอินสแตนซ์ของ globals ของตนเองหรือไม่หรือมีการแชร์ globals หรือไม่

ขอบคุณ


3
โดยโมดูลคุณอาจหมายถึงlibs มีข้อเสนอให้เพิ่มโมดูลลงในมาตรฐาน C ++ พร้อมคำจำกัดความที่แม่นยำยิ่งขึ้นว่าโมดูลจะเป็นอย่างไรและมีความหมายที่แตกต่างจากไลบรารีทั่วไป ณ ตอนนี้
David Rodríguez - น้ำลายไหล

อาควรจะชี้แจงว่า ฉันพิจารณาโครงการต่างๆในโซลูชัน (ฉันทำงานกับสตูดิโอภาพเป็นจำนวนมาก) เป็นโมดูล โมดูลเหล่านี้สร้างขึ้นใน * .lib หรือ * .dll
ราชา

3
@ DavidRodríguez-dribeas คำว่า "โมดูล" เป็นคำศัพท์ทางเทคนิคที่ถูกต้องสำหรับไฟล์ปฏิบัติการแบบสแตนด์อโลน เหมาะสมอย่างยิ่งที่นี่และความหมายถูกต้องและเข้าใจดี จนกว่าจะมีคุณสมบัติมาตรฐานที่ชื่อว่า "โมดูล" คำจำกัดความของมันยังคงเป็นคุณสมบัติดั้งเดิมตามที่ฉันอธิบาย
Mikael Persson

คำตอบ:


177

นี่คือความแตกต่างที่มีชื่อเสียงมากระหว่าง Windows และระบบที่เหมือน Unix

ไม่ว่าอะไรก็ตาม:

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

ดังนั้นปัญหาที่สำคัญที่นี่เป็นจริงการมองเห็น

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

จุดที่ซับซ้อนคือเมื่อคุณมีexternตัวแปรส่วนกลาง ที่นี่ระบบที่เหมือน Windows และ Unix นั้นแตกต่างกันอย่างสิ้นเชิง

ในกรณีของ Windows (.exe และ. dll) externตัวแปรส่วนกลางไม่ได้เป็นส่วนหนึ่งของสัญลักษณ์ที่ส่งออก กล่าวอีกนัยหนึ่งโมดูลที่แตกต่างกันจะไม่ทราบถึงตัวแปรส่วนกลางที่กำหนดไว้ในโมดูลอื่น ๆ ซึ่งหมายความว่าคุณจะได้รับข้อผิดพลาดของตัวเชื่อมโยงหากคุณลองสร้างไฟล์ปฏิบัติการที่ควรจะใช้externตัวแปรที่กำหนดไว้ใน DLL เนื่องจากไม่ได้รับอนุญาต คุณจะต้องจัดเตรียมไฟล์อ็อบเจ็กต์ (หรือไลบรารีแบบคงที่) พร้อมคำจำกัดความของตัวแปรภายนอกนั้นและเชื่อมโยงแบบคงที่กับทั้งไฟล์ปฏิบัติการและ DLL ส่งผลให้มีตัวแปรส่วนกลางที่แตกต่างกันสองตัวแปร (ตัวหนึ่งเป็นของไฟล์ปฏิบัติการและอีกตัวที่เป็นของ DLL )

ในการส่งออกตัวแปรส่วนกลางใน Windows คุณต้องใช้ไวยากรณ์ที่คล้ายกับไวยากรณ์การส่งออก / นำเข้าของฟังก์ชันกล่าวคือ:

#ifdef COMPILING_THE_DLL
#define MY_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_DLL_EXPORT extern "C" __declspec(dllimport)
#endif

MY_DLL_EXPORT int my_global;

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

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

ในที่สุดทั้งสองกรณีสำหรับ Windows หรือ Unix เหมือนระบบที่คุณสามารถทำเวลาทำงานเชื่อมโยงห้องสมุดแบบไดนามิกคือใช้ทั้งLoadLibrary()/ GetProcAddress()/ FreeLibrary()หรือdlopen()/ /dlsym() dlclose()ในกรณีนี้คุณต้องรับตัวชี้ไปยังสัญลักษณ์แต่ละตัวที่คุณต้องการใช้ด้วยตนเองและรวมถึงตัวแปรส่วนกลางที่คุณต้องการใช้ด้วย สำหรับตัวแปรส่วนกลางคุณสามารถใช้GetProcAddress()หรือdlsym()เช่นเดียวกับที่คุณทำสำหรับฟังก์ชันโดยที่ตัวแปรส่วนกลางเป็นส่วนหนึ่งของรายการสัญลักษณ์ที่ส่งออก (ตามกฎของย่อหน้าก่อนหน้า)

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


5
คำตอบที่ดีขอบคุณ! ฉันมีการติดตาม: เนื่องจาก DLL เป็นส่วนของโค้ดและข้อมูลที่มีอยู่ในตัวจึงมีส่วนของกลุ่มข้อมูลที่คล้ายกับไฟล์ปฏิบัติการหรือไม่? ฉันกำลังพยายามทำความเข้าใจว่าข้อมูลนี้ถูกโหลด (to) ที่ไหนและอย่างไรเมื่อมีการใช้ไลบรารีที่แชร์
ราชา

18
@Raja ใช่ DLL มีส่วนข้อมูล ในความเป็นจริงในแง่ของไฟล์ไฟล์ปฏิบัติการและ DLL แทบจะเหมือนกันข้อแตกต่างที่แท้จริงเพียงอย่างเดียวคือแฟล็กที่ตั้งค่าในไฟล์ปฏิบัติการเพื่อบอกว่ามีฟังก์ชัน "หลัก" เมื่อกระบวนการโหลด DLL ส่วนข้อมูลจะถูกคัดลอกที่ใดที่หนึ่งลงในพื้นที่แอดเดรสของกระบวนการและรหัสการเริ่มต้นแบบคงที่ (ซึ่งจะเริ่มต้นตัวแปรส่วนกลางที่ไม่สำคัญ) จะถูกเรียกใช้ภายในพื้นที่แอดเดรสของกระบวนการด้วย การโหลดจะเหมือนกับไฟล์ปฏิบัติการยกเว้นว่าพื้นที่แอดเดรสของกระบวนการจะถูกขยายแทนที่จะสร้างขึ้นมาใหม่
Mikael Persson

4
แล้วตัวแปรคงที่กำหนดไว้ในฟังก์ชันอินไลน์ของคลาสล่ะ? เช่นกำหนด "class A {void foo () {static int st_var = 0;}}" ในไฟล์ส่วนหัวและรวมไว้ในโมดูล A และโมดูล B A / B จะแชร์ st_var เดียวกันหรือแต่ละรายการจะมีสำเนาของตัวเอง
camino

2
@camino หากคลาสถูกเอ็กซ์พอร์ต (เช่นกำหนดด้วย__attribute__((visibility("default")))) A / B จะแชร์ st_var เดียวกัน แต่ถ้ามีการกำหนดคลาสด้วย__attribute__((visibility("hidden")))โมดูล A และโมดูล B จะมีสำเนาของตัวเองไม่ใช้ร่วมกัน
Wei Guo

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