เมื่อสร้างไลบรารีคลาสใน C ++ คุณสามารถเลือกระหว่างไลบรารี dynamic ( .dll
, .so
) และ static ( .lib
, .a
) อะไรคือความแตกต่างระหว่างพวกเขาและเมื่อใดที่เหมาะสมที่จะใช้
เมื่อสร้างไลบรารีคลาสใน C ++ คุณสามารถเลือกระหว่างไลบรารี dynamic ( .dll
, .so
) และ static ( .lib
, .a
) อะไรคือความแตกต่างระหว่างพวกเขาและเมื่อใดที่เหมาะสมที่จะใช้
คำตอบ:
ห้องสมุดคงเพิ่มขนาดของรหัสในไบนารีของคุณ พวกเขาจะโหลดอยู่เสมอและรหัสรุ่นใด ๆ ที่คุณรวบรวมคือเวอร์ชันของรหัสที่จะเรียกใช้
ไลบรารีแบบไดนามิกถูกจัดเก็บและแยกรุ่น เป็นไปได้สำหรับรุ่นของไลบรารีแบบไดนามิกที่จะโหลดซึ่งไม่ใช่แบบดั้งเดิมที่มาพร้อมกับรหัสของคุณหากการอัปเดตนั้นถือว่าเป็นไบนารีที่เข้ากันได้กับเวอร์ชันดั้งเดิม
นอกจากนี้ไลบรารีแบบไดนามิกไม่จำเป็นต้องโหลดซึ่งโดยปกติจะถูกโหลดเมื่อเรียกใช้ครั้งแรกและสามารถใช้ร่วมกันระหว่างส่วนประกอบที่ใช้ไลบรารีเดียวกัน (โหลดข้อมูลหลายครั้งโหลดโค้ดเดียว)
ไลบรารีแบบไดนามิกได้รับการพิจารณาว่าเป็นวิธีที่ดีกว่าโดยส่วนใหญ่ แต่เดิมแล้วพวกเขามีข้อบกพร่องที่สำคัญ (นรกของ Google DLL) ซึ่งมีทั้งหมด แต่ถูกกำจัดโดยระบบปฏิบัติการ Windows รุ่นล่าสุด (โดยเฉพาะ Windows XP)
คนอื่น ๆ ได้อธิบายอย่างเพียงพอว่าห้องสมุดแบบสแตติกคืออะไร แต่ฉันต้องการชี้ให้เห็นบางส่วนของการใช้ห้องสมุดแบบคงที่อย่างน้อยใน Windows:
Singletons:หากมีบางสิ่งที่จำเป็นต้องเป็นโกลบอล / สแตติกและไม่ซ้ำกันให้ระมัดระวังในการวางไว้ในไลบรารี่แบบคงที่ หาก DLLs หลายตัวเชื่อมโยงกับไลบรารี่คงที่พวกเขาแต่ละคนจะได้รับสำเนาซิงเกิลของตนเอง อย่างไรก็ตามหากแอปพลิเคชันของคุณเป็น EXE เดียวโดยไม่มี DLLs ที่กำหนดเองนี่อาจไม่เป็นปัญหา
การลบรหัสที่ไม่ได้อ้างถึง:เมื่อคุณเชื่อมโยงกับไลบรารีคงที่เฉพาะส่วนต่าง ๆ ของไลบรารีแบบคงที่ที่อ้างถึงโดย DLL / EXE ของคุณจะได้รับการเชื่อมโยงเข้ากับ DLL / EXE ของคุณ
ตัวอย่างเช่นถ้าmylib.lib
มีa.obj
และb.obj
และ DLL / EXE ของคุณอ้างอิงเฉพาะฟังก์ชั่นหรือตัวแปรจากa.obj
ทั้งหมดb.obj
จะถูกยกเลิกโดย linker หากb.obj
มีวัตถุทั่วโลก / คงที่คอนสตรัคและ destructors ของพวกเขาจะไม่ได้รับการดำเนินการ หากคอนสตรัคเตอร์ / อุปกรณ์ทำลายเหล่านั้นมีผลข้างเคียงคุณอาจผิดหวังหากไม่มี
ในทำนองเดียวกันหากไลบรารีแบบสแตติกมีจุดเข้าใช้งานพิเศษคุณอาจต้องดูแลว่ามีการรวมไว้จริง ๆ ตัวอย่างของสิ่งนี้ในการเขียนโปรแกรมแบบฝังตัว (โอเคไม่ใช่ Windows) จะเป็นตัวจัดการขัดจังหวะที่ถูกทำเครื่องหมายว่าเป็นที่อยู่เฉพาะ คุณต้องทำเครื่องหมายตัวจัดการขัดจังหวะเป็นจุดเริ่มต้นเพื่อให้แน่ใจว่าไม่ได้ถูกยกเลิก
ผลที่ตามมาก็คือห้องสมุดแบบคงที่อาจมีวัตถุไฟล์ที่ไม่สามารถใช้งานได้อย่างสมบูรณ์เนื่องจากการอ้างอิงที่ไม่ได้รับการแก้ไข แต่จะไม่ทำให้เกิดข้อผิดพลาด linker จนกว่าคุณจะอ้างอิงฟังก์ชันหรือตัวแปรจากไฟล์วัตถุเหล่านั้น สิ่งนี้อาจเกิดขึ้นนานหลังจากเขียนไลบรารี
สัญลักษณ์แก้จุดบกพร่อง:คุณอาจต้องการ PDB แยกต่างหากสำหรับแต่ละไลบรารีคงที่หรือคุณอาจต้องการวางสัญลักษณ์แก้จุดบกพร่องในไฟล์วัตถุเพื่อให้พวกเขาได้รับการรีดลงใน PDB สำหรับ DLL / EXE เอกสาร Visual C ++ อธิบายตัวเลือกที่จำเป็น
RTTI:คุณอาจลงท้ายด้วยtype_info
วัตถุหลายรายการสำหรับคลาสเดียวกันถ้าคุณเชื่อมโยงไลบรารีแบบคงที่เดียวลงใน DLLs หลายตัว หากโปรแกรมของคุณสันนิษฐานว่าtype_info
เป็นข้อมูล "ซิงเกิล" และใช้งาน&typeid()
หรือtype_info::before()
คุณอาจได้ผลลัพธ์ที่ไม่พึงประสงค์และน่าประหลาดใจ
lib คือหน่วยของรหัสที่รวมอยู่ในโปรแกรมของคุณ
dll เป็นหน่วยประมวลผลแบบสแตนด์อโลนของรหัส มันถูกโหลดในกระบวนการก็ต่อเมื่อมีการโทรเข้าไปในรหัสนั้น dll สามารถใช้งานได้หลายแอพพลิเคชั่นและโหลดในหลาย ๆ กระบวนการในขณะที่ยังคงมีรหัสอยู่เพียงหนึ่งสำเนาในฮาร์ดไดรฟ์
ข้อดี dll : สามารถใช้ซ้ำ / แบ่งปันรหัสระหว่างผลิตภัณฑ์ต่าง ๆ ; โหลดในหน่วยความจำกระบวนการตามความต้องการและสามารถยกเลิกการโหลดเมื่อไม่ต้องการ; สามารถอัปเกรดเป็นอิสระจากส่วนที่เหลือของโปรแกรม
ข้อเสีย dll : ผลกระทบต่อประสิทธิภาพของการโหลด dll และการรีบูตรหัส; ปัญหาการกำหนดเวอร์ชัน ("dll Hell")
ข้อดีของ Lib : ไม่มีผลกระทบต่อประสิทธิภาพเนื่องจากมีการโหลดรหัสในกระบวนการเสมอและไม่ได้ถูกลดระดับ ไม่มีปัญหาการกำหนดเวอร์ชัน
Lib ข้อเสีย : ปฏิบัติการ / กระบวนการ "ขยาย" - รหัสทั้งหมดอยู่ในปฏิบัติการของคุณและโหลดเมื่อเริ่มกระบวนการ; ไม่มีการใช้ซ้ำ / ใช้ร่วมกัน - ผลิตภัณฑ์แต่ละชนิดมีรหัสของตนเอง
นอกจากนี้ผลกระทบทางเทคนิคของคงเทียบกับห้องสมุดแบบไดนามิก (คงไฟล์ทุกมัดในไบนารีขนาดใหญ่ VS ห้องสมุดแบบไดนามิกที่ช่วยให้ใช้งานร่วมกันในหมู่ executables รหัสที่แตกต่างกัน) ยังมีผลทางกฎหมาย
ตัวอย่างเช่นหากคุณใช้รหัสลิขสิทธิ์ LGPL และคุณเชื่อมโยงกับห้องสมุด LGPL แบบคงที่ (และสร้างไบนารีใหญ่หนึ่งรายการ) รหัสของคุณจะกลายเป็นรหัสเปิดแหล่งที่มาอัตโนมัติ ( เป็นอิสระ)รหัส LGPL หากคุณเชื่อมโยงกับวัตถุที่ใช้ร่วมกันคุณจะต้อง LGPL เพียงการปรับปรุง / แก้ไขข้อบกพร่องที่คุณทำกับห้องสมุด LGPL เท่านั้น
นี่เป็นปัญหาที่สำคัญยิ่งกว่าหากคุณตัดสินใจรวบรวมแอพพลิเคชั่นมือถือเช่น (ใน Android คุณมีตัวเลือกคงที่ vs ไดนามิกใน iOS ที่คุณทำไม่ได้ - มันคงที่เสมอ)
โปรแกรม C ++ นั้นสร้างขึ้นในสองขั้นตอน
ห้องสมุดคงที่ (.lib) เป็นเพียงชุดของไฟล์. obj และดังนั้นจึงไม่ใช่โปรแกรมที่สมบูรณ์ มันไม่ได้ผ่านขั้นตอนที่สอง (การเชื่อมโยง) ของการสร้างโปรแกรม ในขณะที่กำลังเป็นเหมือน exe และดังนั้นจึงเป็นโปรแกรมที่สมบูรณ์
หากคุณสร้างไลบรารีแบบคงที่จะไม่ได้เชื่อมโยงดังนั้นผู้ใช้ห้องสมุดแบบคงที่ของคุณจะต้องใช้คอมไพเลอร์ตัวเดียวกับที่คุณใช้ (ถ้าคุณใช้ g ++ พวกเขาจะต้องใช้ g ++)
หากคุณสร้าง dll ขึ้นมา (และสร้างขึ้นอย่างถูกต้อง ) คุณได้สร้างโปรแกรมที่สมบูรณ์ที่ผู้ใช้ทุกคนสามารถใช้ได้ไม่ว่าพวกเขาจะใช้คอมไพเลอร์ใด มีข้อ จำกัด หลายประการ แต่ในการส่งออกจาก dll หากต้องการความเข้ากันได้ข้ามคอมไพเลอร์
consumers of your static library will have to use the same compiler that you used
ถ้าห้องสมุดคงใช้ห้องสมุด C ++ #include <iostream>
เช่น
$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
cc -o hello hello.o -L. -ltest
hello.o: hello.c
cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
ar cr libtest.a foo.o foo2.o
foo.o:foo.c
cc -c foo.c
foo2.o:foo.c
cc -c foo2.c
clean:
rm -f foo.o foo2.o libtest.a hello.o
$$:~/static [38]>
$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
cc -o hello hello.o -L`pwd` -ltest
hello.o:
cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
cc -c -b foo.c
foo2.o:foo.c
cc -c -b foo2.c
clean:
rm -f libtest.sl foo.o foo
2.o hello.o
$$:~/dynamic [50]>
ห้องสมุดคงได้รับการรวบรวมลงในลูกค้า . lib ถูกใช้ในเวลาคอมไพล์และเนื้อหาของไลบรารีกลายเป็นส่วนหนึ่งของการปฏิบัติการที่ใช้งานได้
ไลบรารีแบบไดนามิกถูกโหลดเมื่อรันไทม์และไม่ได้คอมไพล์ลงในไคลเอ็นต์ที่เรียกทำงานได้ ไลบรารีแบบไดนามิกมีความยืดหยุ่นมากขึ้นเนื่องจากไคลเอนต์หลายตัวสามารถโหลด DLL และใช้งานได้ สิ่งนี้ยังช่วยรักษาขนาดโดยรวมและความสามารถในการบำรุงรักษารหัสลูกค้าของคุณให้น้อยที่สุด
คุณควรคิดอย่างรอบคอบเกี่ยวกับการเปลี่ยนแปลงเมื่อเวลาผ่านไปการกำหนดเวอร์ชันความเสถียรความเข้ากันได้และอื่น ๆ
หากมีแอพสองตัวที่ใช้รหัสที่แชร์คุณต้องการบังคับให้แอพเหล่านั้นเปลี่ยนไปด้วยกันหรือไม่ในกรณีที่จำเป็นต้องใช้งานร่วมกันได้ จากนั้นใช้ dll exe ทั้งหมดจะใช้รหัสเดียวกัน
หรือคุณต้องการแยกพวกเขาออกจากกันเพื่อให้คุณสามารถเปลี่ยนหนึ่งและมั่นใจว่าคุณไม่ได้แตกอีก จากนั้นใช้ lib แบบคงที่
DLL นรกคือเมื่อคุณน่าจะใช้ lib แบบคงที่ แต่คุณใช้ dll แทนและไม่ใช่ exes ทั้งหมดที่จะเข้ากันได้กับมัน
ไลบรารีแบบสแตติกจะต้องเชื่อมโยงกับการปฏิบัติการขั้นสุดท้าย มันจะกลายเป็นส่วนหนึ่งของปฏิบัติการและตามมันไปทุกที่ ไลบรารีแบบไดนามิกจะถูกโหลดทุกครั้งที่มีการเรียกใช้งานและยังคงแยกจากไฟล์ที่เรียกใช้งานเป็นไฟล์ DLL
คุณจะใช้ DLL เมื่อคุณต้องการที่จะเปลี่ยนการทำงานของห้องสมุดโดยไม่ต้องเชื่อมโยงไฟล์ปฏิบัติการใหม่ (เพียงแค่แทนที่ไฟล์ DLL โดยไม่ต้องเปลี่ยนไฟล์ปฏิบัติการ)
คุณจะใช้ไลบรารีแบบคงที่ทุกครั้งที่คุณไม่มีเหตุผลในการใช้ไลบรารีแบบไดนามิก
บทความของ Ulrich Drepper ใน " How to Write Libraries ที่ใช้ร่วมกัน " เป็นแหล่งข้อมูลที่ดีที่ให้รายละเอียดว่าจะใช้ประโยชน์จากไลบรารีที่แชร์ได้อย่างไรหรือสิ่งที่เขาอ้างถึงว่าเป็น "Dynamic Shared Objects" (DSOs) มันเน้นเพิ่มเติมเกี่ยวกับไลบรารี่ที่แชร์ในรูปแบบไบนารี่ของELFแต่การสนทนาบางอย่างก็เหมาะสำหรับ Windows DLL เช่นกัน
สำหรับการอภิปรายที่ยอดเยี่ยมของหัวข้อนี้อ่านบทความของ Sun นี้
มันจะเข้าสู่ผลประโยชน์ทั้งหมดรวมถึงความสามารถในการแทรกห้องสมุดแทรก รายละเอียดเพิ่มเติมเกี่ยวกับ interposing สามารถพบได้ในบทความนี้ที่นี่
จริง ๆ แล้วการแลกเปลี่ยนที่คุณทำอยู่ (ในโครงการขนาดใหญ่) นั้นอยู่ในช่วงโหลดครั้งแรกห้องสมุดจะทำการเชื่อมโยงในคราวเดียวหรืออย่างอื่นการตัดสินใจที่ต้องทำก็คือลิงก์จะใช้เวลานานพอที่คอมไพเลอร์ต้องการ กัดกระสุนและทำล่วงหน้าหรือตัวเชื่อมโยงแบบไดนามิกสามารถทำได้ในขณะโหลด
หากไลบรารีของคุณจะถูกใช้ร่วมกันระหว่างหลาย ๆ โปรแกรมมันมักจะเหมาะสมที่จะทำให้มันเป็นแบบไดนามิกเพื่อลดขนาดของไฟล์โปรแกรม มิฉะนั้นทำให้คงที่แน่นอน
มีข้อเสียหลายประการในการใช้ dll มีค่าใช้จ่ายเพิ่มเติมสำหรับการโหลดและยกเลิกการโหลด นอกจากนี้ยังมีการพึ่งพาเพิ่มเติม หากคุณเปลี่ยน dll เพื่อให้มันเข้ากันไม่ได้กับ executalbes ของคุณพวกเขาจะหยุดทำงาน ในทางกลับกันถ้าคุณเปลี่ยนไลบรารีแบบสแตติกเอ็กซีคิวต์ที่คอมไพล์แล้วซึ่งใช้เวอร์ชั่นเก่าจะไม่ได้รับผลกระทบ
หากไลบรารี่เป็นแบบสแตติกเมื่อถึงเวลาเชื่อมโยงโค้ดจะถูกลิงก์ด้วยไฟล์ปฏิบัติการของคุณ สิ่งนี้ทำให้ความสามารถในการปฏิบัติการของคุณมีขนาดใหญ่กว่า
หากไลบรารี่เป็นแบบไดนามิกเมื่อถึงเวลาลิงค์การอ้างอิงไปยังเมธอดที่ต้องการจะถูกสร้างขึ้นในไฟล์ปฏิบัติการของคุณ ซึ่งหมายความว่าคุณต้องจัดส่งแฟ้มที่ปฏิบัติการได้และไลบรารีแบบไดนามิกของคุณ คุณควรพิจารณาด้วยว่าการเข้าถึงรหัสในห้องสมุดปลอดภัยหรือไม่ที่อยู่โหลดที่ต้องการระหว่างสิ่งอื่น ๆ
ถ้าคุณสามารถอยู่กับไลบรารีแบบคงที่ไปกับไลบรารีแบบคงที่
เราใช้ DLL จำนวนมาก (> 100) ในโครงการของเรา DLL เหล่านี้มีการพึ่งพาซึ่งกันและกันดังนั้นเราจึงเลือกการตั้งค่าการเชื่อมโยงแบบไดนามิก อย่างไรก็ตามมันมีข้อเสียดังต่อไปนี้:
บางทีการตั้งค่าที่ดีกว่าคือการทำให้ทุกอย่างเป็นสแตติกไลบรารี่ (และคุณมีเพียงไฟล์เดียวที่รันได้) ใช้ได้เฉพาะเมื่อไม่มีการทำซ้ำรหัส ดูเหมือนว่าการทดสอบจะสนับสนุนสมมติฐานนี้ แต่ฉันไม่พบใบเสนอราคาอย่างเป็นทางการของ MSDN ตัวอย่างเช่นทำ 1 exe ด้วย:
รหัสและตัวแปรของ shared_lib2 ควรมีอยู่ในการปฏิบัติการขั้นสุดท้ายที่ผสานได้เพียงครั้งเดียว ใครช่วยคำถามนี้ได้บ้าง
ห้องสมุดคงเป็นคลังเก็บที่มีรหัสวัตถุสำหรับห้องสมุดเมื่อเชื่อมโยงไปยังโปรแกรมที่รหัสจะรวบรวมในปฏิบัติการ ไลบรารีที่ใช้ร่วมกันนั้นแตกต่างกันโดยที่พวกเขาจะไม่ถูกคอมไพล์ลงในแฟ้มที่ปฏิบัติการได้ แต่ตัวเชื่อมโยงแบบไดนามิกจะค้นหาบางไดเรกทอรีที่ต้องการไลบรารีที่ต้องการจากนั้นโหลดลงในหน่วยความจำ มากกว่าหนึ่งปฏิบัติการสามารถใช้ห้องสมุดสาธารณะเดียวกันในเวลาเดียวกันจึงลดการใช้หน่วยความจำและขนาดปฏิบัติการ อย่างไรก็ตามมีไฟล์เพิ่มเติมที่จะแจกจ่ายด้วยไฟล์เรียกทำงานได้ คุณต้องตรวจสอบให้แน่ใจว่ามีการติดตั้งไลบรารีลงในระบบการใช้งานที่ซึ่งตัวเชื่อมโยงสามารถค้นหาได้การเชื่อมโยงแบบสแตติกจะกำจัดปัญหานี้ แต่ส่งผลให้ไฟล์ปฏิบัติการขนาดใหญ่ขึ้น
หากคุณทำงานกับโครงการฝังตัวหรือแพลตฟอร์มแบบคงที่ไลบรารีแบบคงที่เป็นหนทางเดียวที่จะไปได้หลายครั้งที่พวกเขาจะไม่ยุ่งยากในการรวบรวมแอปพลิเคชันของคุณ ยังมีโครงการและ makefile ที่รวมทุกอย่างทำให้ชีวิตมีความสุข
ฉันจะให้กฎทั่วไปง่ายๆว่าถ้าคุณมี codebase ขนาดใหญ่ทั้งหมดนี้สร้างขึ้นจากห้องสมุดระดับล่าง (เช่นกรอบ Utils หรือ Gui) ซึ่งคุณต้องการแบ่งพาร์ติชันให้เป็นไลบรารีที่จัดการได้มากกว่า ห้องสมุดแบบไดนามิกไม่ได้ซื้ออะไรให้คุณจริงๆและมีความประหลาดใจน้อยลง - จะมีอินสแตนซ์เดียวของอินสแตนซ์เดียว
หากคุณมีห้องสมุดที่แยกออกจากส่วนอื่น ๆ ของ codebase (เช่นไลบรารีของบุคคลที่สาม) ให้ลองสร้าง dll ดู หากห้องสมุดเป็น LGPL คุณอาจต้องใช้ dll เนื่องจากเงื่อนไขการให้สิทธิ์ใช้งาน