ความเข้ากันได้ของภาษา C และ C++
ภาษาโปรแกรมCและC++ มีความสัมพันธ์กันอย่างใกล้ชิด แต่ก็มีความแตกต่างที่สำคัญหลายประการ C++ เริ่มต้นจากการแยก สาขาของภาษา C ในยุค แรกๆ ก่อน ที่จะ มีการกำหนดมาตรฐานและได้รับการออกแบบให้เข้ากันได้กับคอมไพเลอร์ C ในยุคนั้นโดยส่วนใหญ่[ 1 ] [ 2 ] ด้วยเหตุนี้ เครื่องมือพัฒนาสำหรับทั้งสองภาษา (เช่นIDEและคอมไพเลอร์ ) จึงมักถูกรวมเข้าไว้ในผลิตภัณฑ์เดียว โดยโปรแกรมเมอร์สามารถระบุ C หรือ C++ เป็นภาษาต้นฉบับได้
อย่างไรก็ตาม C ไม่ใช่ส่วนย่อยของ C++ [ 3 ]และโปรแกรม C ที่ไม่ธรรมดาจะไม่สามารถคอมไพล์เป็นโค้ด C++ ได้หากไม่มีการแก้ไข ในทำนองเดียวกัน C++ นำเสนอคุณสมบัติมากมายที่ไม่มีใน C และในทางปฏิบัติโค้ดเกือบทั้งหมดที่เขียนใน C++ ไม่ใช่โค้ด C ที่สอดคล้อง บทความนี้มุ่งเน้นไปที่ความแตกต่างที่ทำให้โค้ด C ที่สอดคล้องกลายเป็นโค้ด C++ ที่ไม่ถูกต้อง หรือสอดคล้อง/ถูกต้องในทั้งสองภาษาแต่มีพฤติกรรมที่แตกต่างกันใน C และ C++
Bjarne Stroustrupผู้สร้าง C++ ได้เสนอแนะ[ 4 ]ว่าความไม่เข้ากันระหว่าง C และ C++ ควรลดลงให้มากที่สุดเท่าที่จะเป็นไปได้ เพื่อเพิ่มความสามารถในการทำงานร่วมกันระหว่างสองภาษาให้สูงสุด คนอื่นๆ โต้แย้งว่า เนื่องจาก C และ C++ เป็นภาษาที่แตกต่างกันสองภาษา ความเข้ากันได้ระหว่างกันจึงมีประโยชน์แต่ไม่สำคัญ ตามความเห็นของกลุ่มนี้ ความพยายามในการลดความไม่เข้ากันไม่ควรขัดขวางความพยายามในการปรับปรุงแต่ละภาษาแยกกัน เหตุผลอย่างเป็นทางการสำหรับมาตรฐาน C ปี 1999 ( C99 ) "รับรองหลักการของการรักษาส่วนย่อยร่วมที่ใหญ่ที่สุด" ระหว่าง C และ C++ "ในขณะที่ยังคงรักษาความแตกต่างระหว่างกันและอนุญาตให้พัฒนาแยกจากกัน" และระบุว่าผู้เขียน "พอใจที่จะให้ C++ เป็นภาษาที่ใหญ่และทะเยอทะยาน" [ 5 ]
ส่วนเพิ่มเติมหลายอย่างของ C99 ไม่ได้รับการสนับสนุนในมาตรฐาน C++ ปัจจุบัน หรือขัดแย้งกับคุณสมบัติของ C++ เช่นอาร์เรย์ที่มีความยาวแปรผัน ประเภท จำนวนเชิงซ้อนดั้งเดิมและrestrictตัวกำหนดคุณสมบัติประเภทในทางกลับกัน C ได้ค่อยๆ ลดความไม่เข้ากันอื่นๆ บางประการโดยการรวมคุณสมบัติของ C++ เช่น ความคิดเห็นบรรทัดเดียว//และการประกาศและโค้ดแบบผสมใน C99 [ 6 ]และการเพิ่มแอตทริบิวต์[ 7 ]การอนุมานประเภทด้วยคำautoหลัก[ 8 ]และnullptrค่าคงที่ใน C23 [ 9 ]
โครงสร้างที่ใช้ได้ในภาษา C แต่ใช้ไม่ได้ในภาษา C++
C++ บังคับใช้กฎการกำหนดประเภทที่เข้มงวดกว่า (ไม่มีการละเมิดระบบประเภทคงที่โดยปริยาย[ 1 ] ) และข้อกำหนดการเริ่มต้น (การบังคับใช้ในเวลาคอมไพล์ว่าตัวแปรในขอบเขตจะไม่ถูกบิดเบือนการเริ่มต้น) [ 10 ]มากกว่า C ดังนั้นโค้ด C ที่ถูกต้องบางส่วนจึงไม่ถูกต้องใน C++ เหตุผลสำหรับสิ่งเหล่านี้มีให้ในภาคผนวก C.1 ของมาตรฐาน ISO C++ [ 11 ]
- ความแตกต่างที่พบได้ทั่วไปอย่างหนึ่งคือ C มีการกำหนดประเภทแบบอ่อน กว่า ในส่วนของพอยเตอร์ โดยเฉพาะอย่างยิ่ง C อนุญาต
void *ให้กำหนดพอยเตอร์ให้กับประเภทพอยเตอร์ใดก็ได้โดยไม่ต้องแปลงประเภท ในขณะที่ C++ ไม่ได้สำนวน นี้ มักปรากฏในโค้ด C ที่ใช้mallocการจัดสรรหน่วยความจำ[ 12 ]หรือในการส่งผ่านพอยเตอร์บริบทไปยัง API pthreads ของ POSIX และเฟรมเวิร์กอื่นๆ ที่เกี่ยวข้องกับการเรียกกลับตัวอย่างเช่น สิ่งต่อไปนี้ถูกต้องใน C แต่ไม่ถูกต้องใน C++:void * ptr ; // การแปลงโดยปริยายจาก void * เป็น int * int * i = ptr ;
หรือในทำนองเดียวกัน:
int * j = malloc ( 5 * sizeof * j ); // การแปลงโดยปริยายจาก void * เป็น int *
เพื่อให้โค้ดคอมไพล์ได้ทั้งในภาษา C และ C++ ต้องใช้การแปลงแบบชัดเจนดังต่อไปนี้ (โดยมีข้อควรระวังบางประการในทั้งสองภาษา): [ 13 ]
void * ptr ; int * i = ( int * ) ptr ; int * j = ( int * ) malloc ( 5 * sizeof * j );
- ภาษา C++ มีกฎที่ซับซ้อนกว่าเกี่ยวกับการกำหนดค่าตัวชี้ โดยมีการเพิ่มคุณสมบัติพิเศษ เนื่องจากอนุญาตให้กำหนดค่าให้กับตัวแปร
int **ได้const int *const *แต่ไม่อนุญาตให้กำหนดค่าที่ไม่ปลอดภัยให้กับตัวแปรconst int **ในขณะที่ภาษา C ไม่อนุญาตให้ทำทั้งสองอย่าง (ถึงแม้ว่าโดยปกติแล้วคอมไพเลอร์จะแสดงเพียงคำเตือนก็ตาม) - C++ เปลี่ยนแปลง ฟังก์ชัน ไลบรารีมาตรฐานของ C บาง ฟังก์ชันเพื่อเพิ่มฟังก์ชันโอเวอร์โหลดเพิ่มเติมพร้อม
constตัวกำหนดประเภทเช่นstrchrการส่งคืนค่าchar *ใน C ในขณะที่ C++ ทำงานราวกับว่ามีฟังก์ชันโอเวอร์โหลดสองฟังก์ชัน (สำหรับchar *และconst char *) ในC23มีการใช้การเลือกทั่วไปเพื่อให้พฤติกรรมของ C คล้ายกับ C++ มากขึ้น[ 14 ] - C++ ยังมีความเข้มงวดมากกว่าในการแปลงค่าเป็น enum: int ไม่สามารถแปลงเป็น enum โดยปริยายได้เหมือนใน C นอกจากนี้ ก่อนC23 ค่าคงที่ของการแจงนับ (
enumenumerator) จะมีชนิดเป็น int เสมอintใน C ในขณะที่ใน C++ (และ C23 หรือเวอร์ชันที่ใหม่กว่า) ค่าคงที่ของการแจงนับจะมีชนิดเป็น int ที่แตกต่างกัน และอาจมีขนาดแตกต่างจาก intintทั่วไป - ในภาษา C++
constตัวแปรจะต้องได้รับการกำหนดค่าเริ่มต้น แต่ในภาษา C ไม่จำเป็นต้องทำเช่นนั้น - คอมไพเลอร์ C++ ห้ามไม่ให้คำสั่ง goto หรือ switch ข้ามการกำหนดค่าเริ่มต้น ดังเช่นในโค้ด C99 ต่อไปนี้:
void fn ( void ) { goto flack ; int i = 1 ; flack : printf ( "ถึง flack แล้ว" ); }
- แม้ว่าไวยากรณ์จะถูกต้อง แต่ ผลลัพธ์จะทำให้เกิดพฤติกรรมที่ไม่แน่นอนใน C++ หาก เฟรมสแต็ก
longjmp()ที่กระโดดข้ามไปนั้นมีวัตถุที่มีตัวทำลายที่ไม่ธรรมดา[ 15 ]การใช้งาน C++ มีอิสระที่จะกำหนดพฤติกรรมเพื่อให้มีการเรียกตัวทำลาย อย่างไรก็ตาม สิ่งนี้จะขัดขวางการใช้งานบางอย่างที่อาจถูกต้อง เช่น การใช้งานเธรดหรือโครูทีนที่สลับระหว่างสแต็กการเรียกที่แยกจากกัน— เมื่อกระโดดจากสแต็กการเรียกด้านล่างไปยังสแต็กการเรียกด้านบนในพื้นที่ที่อยู่ส่วนกลาง ตัวทำลายจะถูกเรียกสำหรับทุกวัตถุในสแต็กการเรียกด้านล่าง ไม่มีปัญหาดังกล่าวใน Clongjmp()longjmp() - ภาษา C อนุญาตให้มีการกำหนดค่าชั่วคราวหลายค่าสำหรับตัวแปรส่วนกลางตัวเดียวในหน่วยการแปล เดียว ซึ่งถือว่าไม่ถูกต้องเนื่องจากเป็นการ ละเมิด ODRในภาษา C++
int n ; int n = 10 ;
- ในภาษา C การประกาศชนิดข้อมูลใหม่ที่มีชื่อเดียวกันกับชนิดข้อมูลที่มีอยู่แล้ว เช่นหรือ
structนั้นถูกต้อง แต่ไม่ถูกต้องในภาษา C++ เพราะในภาษา C ชนิดข้อมูลเช่น หรือ จะต้องระบุให้ชัดเจนทุกครั้งที่มีการอ้างอิงถึงชนิดข้อมูลนั้น ในขณะที่ในภาษา C++ การประกาศชนิดข้อมูลดังกล่าวทั้งหมดจะระบุtypedef ไว้ โดยปริยายunionenumstructunionenumenum Bool { FALSE , TRUE };typedef int Bool ; // ในภาษา C, Bool และ enum Bool นั้นแตกต่างกัน
- การประกาศฟังก์ชันแบบไม่ใช้ต้นแบบ ("สไตล์ K&R") ถือว่าไม่ถูกต้องใน C++ แต่ยังคงถูกต้องใน C จนถึง C23 [ 16 ] [ 17 ]แม้ว่าจะถือว่าล้าสมัยไปแล้วตั้งแต่การกำหนดมาตรฐานดั้งเดิมของ C ในปี 1990 (คำว่า "ล้าสมัย" เป็นคำที่กำหนดไว้ในมาตรฐาน ISO C ซึ่งหมายถึงคุณลักษณะที่ "อาจพิจารณาถอนออกในการแก้ไขมาตรฐานในอนาคต") ในทำนองเดียวกัน การประกาศฟังก์ชันโดยปริยาย (การใช้ฟังก์ชันที่ไม่ได้ประกาศ) ไม่ได้รับอนุญาตใน C++ และถือว่าไม่ถูกต้องใน C ตั้งแต่ปี 1999
- ในภาษา C จนถึง C23 [ 18 ]การประกาศฟังก์ชันที่ไม่มีพารามิเตอร์ เช่น
int foo();หมายความว่าไม่ได้ระบุพารามิเตอร์ ดังนั้นจึงสามารถเรียกใช้ฟังก์ชันดังกล่าวโดยมีอาร์กิวเมนต์ หนึ่งตัวหรือมากกว่า เช่น ได้foo(42, "hello world")ในทางตรงกันข้าม ในภาษา C++ ต้นแบบฟังก์ชันที่ไม่มีอาร์กิวเมนต์หมายความว่าฟังก์ชันนั้นไม่รับอาร์กิวเมนต์ และการเรียกใช้ฟังก์ชันดังกล่าวโดยมีอาร์กิวเมนต์นั้นไม่ถูกต้อง ในภาษา C วิธีที่ถูกต้องในการประกาศฟังก์ชันที่ไม่รับอาร์กิวเมนต์คือการใช้ 'void' เช่นint foo(void);ซึ่งก็ถูกต้องในภาษา C++ เช่นกัน ต้นแบบฟังก์ชันว่างเปล่าเป็นคุณสมบัติที่เลิกใช้แล้วใน C99 (เช่นเดียวกับใน C89) - ทั้งในภาษา C และ C++ สามารถกำหนด
structประเภทซ้อนกันได้ แต่ขอบเขตจะถูกตีความแตกต่างกัน: ใน C++ ประเภทซ้อนกันstructจะถูกกำหนดเฉพาะภายในขอบเขต/เนมสเปซของประเภทภายนอกเท่านั้นstructในขณะที่ในภาษา C โครงสร้างภายในจะถูกกำหนดอยู่นอกโครงสร้างภายนอกด้วย - ภาษา C อนุญาตให้ประกาศชนิดข้อมูล
struct,union, และ ในต้นแบบฟังก์ชันได้ ในขณะที่ภาษา C++ ไม่ได้enum
C99และC11ได้เพิ่มคุณสมบัติเพิ่มเติมหลายอย่างให้กับภาษา C ซึ่งยังไม่ได้ถูกรวมเข้าไว้ในมาตรฐาน C++ ในC++20เช่น จำนวนเชิงซ้อน อาร์เรย์ที่มีความยาวแปรผันได้ (จำนวนเชิงซ้อนและอาร์เรย์ที่มีความยาวแปรผันได้ถูกกำหนดให้เป็นส่วนขยายเสริมใน C11) สมาชิก อาร์เรย์ที่ยืดหยุ่น คำหลัก restrict ตัว กำหนดคุณสมบัติพารามิเตอร์อาร์เรย์ และตัวอักษรประกอบ
- การคำนวณเลขคณิตเชิงซ้อนโดยใช้ ชนิดข้อมูลพื้นฐาน
float complexและdouble complexชนิดข้อมูลดั้งเดิมถูกเพิ่มใน มาตรฐาน C99โดยใช้_Complexคำหลักและcomplexมาโครอำนวยความสะดวก ใน C++ การคำนวณเลขคณิตเชิงซ้อนสามารถทำได้โดยใช้คลาสจำนวนเชิงซ้อน แต่ทั้งสองวิธีไม่เข้ากันในโค้ด (อย่างไรก็ตาม มาตรฐานตั้งแต่C++11 เป็นต้นไป กำหนดให้ต้องเข้ากันได้แบบไบนารี) [ 19 ] - อาร์เรย์ที่มีความยาวแปรผัน คุณสมบัตินี้ทำให้ตัวดำเนินการsizeof อาจไม่ทำงานในเวลาคอมไพล์ [ 20 ]
void foo ( size_t x , int a [ * ]); // การประกาศ VLAvoid foo ( size_t x , int a [ x ]) { printf ( "%zu \n " , sizeof a ); // เหมือนกับ sizeof(int *) char s [ x * 2 ]; printf ( "%zu \n " , sizeof s ); // จะพิมพ์ค่าของ x * 2 }
- สมาชิกตัวสุดท้ายของโครงสร้างประเภท C99 ที่มีสมาชิกมากกว่าหนึ่งตัว อาจเป็นสมาชิกอาร์เรย์แบบยืดหยุ่นซึ่งมีรูปแบบทางไวยากรณ์เป็นอาร์เรย์ที่มีความยาวไม่ระบุ จุดประสงค์คล้ายกับอาร์เรย์ที่มีความยาวแปรผัน แต่ VLA ไม่สามารถปรากฏในคำจำกัดความของประเภทได้ และแตกต่างจาก VLA ตรงที่สมาชิกอาร์เรย์แบบยืดหยุ่นไม่มีขนาดที่กำหนดไว้ ISO C++ ไม่มีคุณสมบัติดังกล่าว ตัวอย่าง:
struct X { int m ; long n ; char bytes []; };
- ตัว
restrictกำหนดคุณสมบัติประเภทที่กำหนดไว้ใน C99 ไม่ได้รวมอยู่ในมาตรฐาน C++03 แต่คอมไพเลอร์หลักส่วนใหญ่ เช่นGNU Compiler Collection [ 21 ] Microsoft Visual C++และIntel C++ Compilerมีฟังก์ชันที่คล้ายกันเป็นส่วนขยาย - การกำหนดคุณสมบัติพารามิเตอร์แบบอาร์เรย์ในฟังก์ชันนั้นรองรับในภาษา C แต่ไม่รองรับในภาษา C++
int foo ( int a [ const ]); // เทียบเท่ากับ int *const a int bar ( char s [ static 5 ]); // ระบุว่า s มีความยาวอย่างน้อย 5 ตัวอักษร
- ฟังก์ชันการทำงานของตัวอักษรประกอบในภาษา C ได้รับการขยายให้ครอบคลุมทั้งประเภทข้อมูลพื้นฐานและประเภทข้อมูลที่ผู้ใช้กำหนดเองโดยไวยากรณ์การเริ่มต้นรายการใน C++11 แม้ว่าจะมีความแตกต่างทางไวยากรณ์และความหมายอยู่บ้างก็ตาม
ใน C:
struct X a = ( struct X ){ 4 , 6 }; // ใน C++ จะเทียบเท่ากับ X{4, 6} รูปแบบไวยากรณ์ C ที่ใช้ใน C99 ได้รับการสนับสนุนเป็นส่วนขยายในคอมไพเลอร์ GCC และ Clang C++ foo ( & ( struct X ){ 4 , 6 }); // อ็อบเจ็กต์จะถูกจัดสรรในสแต็กและสามารถส่งที่อยู่ไปยังฟังก์ชันได้ ซึ่งไม่ได้รับการสนับสนุนใน C++ถ้า( memcmp ( d , ( int []){ 8 , 6 , 7 , 5 , 3 , 0 , 9 }, n ) == 0 ) { // ... }
ในภาษา C++:
using Digits = int []; if ( std :: memcmp ( d , Digits { 8 , 6 , 7 , 5 , 3 , 0 , 9 }, n ) == 0 ) { // ... }
- ตัวกำหนดค่าเริ่มต้นสำหรับอาร์เรย์นั้นใช้ได้เฉพาะในภาษาซีเท่านั้น:
char s [ 20 ] = { [ 0 ] = 'a' , [ 8 ] = 'g' }; // อนุญาตใน C แต่ไม่อนุญาตใน C++
- ฟังก์ชันที่ไม่ส่งคืนค่าสามารถระบุคำอธิบายประกอบได้โดยใช้แอตทริบิวต์ใน C++ ในขณะที่ C ใช้คีย์เวิร์ดที่แตกต่างกันใน C23 ไวยากรณ์แอตทริบิวต์ถูกนำมาใช้ในขณะที่ถูกยกเลิก[ 22 ]
[[noreturn]]_Noreturn_Noreturn - C2Y นำเสนอลูปที่มีชื่อและคำสั่ง break ที่มีชื่อ (คล้ายกับJava ) คุณสมบัตินี้ยังไม่ได้ถูกเพิ่มลงใน C++ ณC++ 26 [ 23 ]
- ภาษา C ใช้
_Genericการเลือก (selection) เพื่อให้สามารถตัดสินใจเกี่ยวกับนิพจน์ได้ในระหว่างการคอมไพล์ ซึ่งอาจมองได้ว่าเป็นการจำลองการโอเวอร์โหลดฟังก์ชัน ส่วนภาษา C++ ไม่จำเป็นต้องใช้วิธีนี้ เนื่องจากภาษา C++ รองรับการโอเวอร์โหลดฟังก์ชันและการกำหนดค่าเฉพาะของเทมเพลตอยู่แล้ว - C23 แนะนำ
typeofตัวดำเนินการที่สร้างประเภทได้ทั้งแบบชนิดหรือแบบนิพจน์ ใน C++ คุณสมบัติที่เทียบเท่ากันคือdecltype.
C++ เพิ่มคีย์เวิร์ดเพิ่มเติมจำนวนมากเพื่อรองรับคุณสมบัติใหม่ ๆ ทำให้โค้ด C ที่ใช้คีย์เวิร์ดเหล่านั้นสำหรับตัวระบุไม่ถูกต้องใน C++ ตัวอย่างเช่น โค้ดต่อไปนี้เป็นโค้ด C ที่ถูกต้อง แต่ถูกปฏิเสธโดยคอมไพเลอร์ C++ เนื่องจากคีย์เวิร์ด `id`, `id` templateและnew` id` classเป็นคำสงวน
struct template { int new ; struct template * class ; };โครงสร้างที่มีพฤติกรรมแตกต่างกันในภาษา C และ C++
มีโครงสร้างทางไวยากรณ์บางอย่างที่ใช้ได้ทั้งในภาษา C และ C++ แต่ให้ผลลัพธ์ที่แตกต่างกันในสองภาษานี้
- ตัวอักษรลิเทอรัลเช่น
'a'มีชนิดข้อมูลเป็นintในภาษา C และเป็นชนิดข้อมูลเป็นcharในภาษา C++ ซึ่งหมายความว่าsizeof 'a'โดยทั่วไปแล้วจะให้ผลลัพธ์ที่แตกต่างกันในสองภาษา: ใน C++ จะเป็น1ในขณะที่ใน C จะเป็นsizeof(int)ผลที่ตามมาอีกประการหนึ่งจากความแตกต่างของชนิดข้อมูลนี้คือ ใน C'a'จะเป็นนิพจน์ที่มีเครื่องหมายเสมอ ไม่ว่าcharจะเป็นชนิดข้อมูลที่มีเครื่องหมายหรือไม่มีเครื่องหมายก็ตาม ในขณะที่ใน C++ นั้นขึ้นอยู่กับการใช้งานของคอมไพเลอร์ - ภาษา C++ กำหนดการเชื่อมโยงภายในให้กับตัวแปรที่มีขอบเขตเฉพาะเนมสเปซ
constเว้นแต่จะมีการประกาศไว้อย่างชัดเจนexternซึ่งต่างจากภาษา C ที่externค่าเริ่มต้นคือการเชื่อมโยงแบบไฟล์สำหรับเอนทิตีทั้งหมด ในทางปฏิบัติ สิ่งนี้ไม่ได้นำไปสู่การเปลี่ยนแปลงความหมายโดยไม่แจ้งให้ทราบระหว่างโค้ด C และ C++ ที่เหมือนกัน แต่จะนำไปสู่ข้อผิดพลาดในการคอมไพล์หรือการเชื่อมโยงแทน - ในภาษา C การใช้ฟังก์ชันแบบอินไลน์จำเป็นต้องเพิ่มการประกาศต้นแบบของฟังก์ชันด้วยตนเองโดยใช้คีย์เวิร์ด `extern` ในหน่วยการแปลเพียงหน่วยเดียวเพื่อให้แน่ใจว่ามีการเชื่อมโยงเวอร์ชันที่ไม่ใช่แบบอินไลน์เข้ามา ในขณะที่ภาษา C++ จัดการเรื่องนี้โดยอัตโนมัติ โดยละเอียดแล้ว ภาษา C แยกความแตกต่างระหว่างการนิยาม
inlineฟังก์ชัน สองประเภท ได้แก่ การนิยามภายนอกทั่วไป (ซึ่งexternใช้ `external` อย่างชัดเจน) และการนิยามแบบอินไลน์ ในทางกลับกัน ภาษา C++ ให้เฉพาะการนิยามแบบอินไลน์สำหรับฟังก์ชันแบบอินไลน์เท่านั้น ในภาษา C การนิยามแบบอินไลน์คล้ายกับการนิยามภายใน (เช่น แบบสแตติก) ตรงที่สามารถอยู่ร่วมกันในโปรแกรมเดียวกันกับการนิยามภายนอกหนึ่งรายการและการนิยามภายในและแบบอินไลน์จำนวนเท่าใดก็ได้ของฟังก์ชันเดียวกันในหน่วยการแปลอื่นๆ ซึ่งทั้งหมดอาจแตกต่างกันได้ นี่เป็นข้อพิจารณาที่แยกต่างหากจากการเชื่อมโยงของฟังก์ชัน แต่ไม่ใช่ข้อพิจารณาที่เป็นอิสระ คอมไพเลอร์ของภาษา C มีดุลยพินิจในการเลือกใช้ระหว่างการนิยามแบบอินไลน์และการนิยามภายนอกของฟังก์ชันเดียวกันเมื่อทั้งสองแบบปรากฏให้เห็น อย่างไรก็ตาม C++ กำหนดว่า หากมีการประกาศฟังก์ชันที่มีการเชื่อมโยงภายนอกinlineในหน่วยการแปลใดๆ ฟังก์ชันนั้นจะต้องถูกประกาศ (และกำหนดความหมาย) ในทุกหน่วยการแปลที่ใช้งาน และคำจำกัดความทั้งหมดของฟังก์ชันนั้นจะต้องเหมือนกันตามหลักการ ODR ฟังก์ชันอินไลน์แบบสแตติกทำงานเหมือนกันในภาษา C และ C++ - ทั้งภาษา C (ตั้งแต่ C99) และ C++ ต่างก็มีชนิดข้อมูลบูลีน
boolที่มีค่าคงที่trueแต่falseมีการกำหนดนิยามที่แตกต่างกัน- ในภาษา C++
boolเป็นชนิดข้อมูลพื้นฐานและเป็นคำสงวน - ใน C99 มีการแนะนำคีย์เวิร์ดใหม่
_Bool`boolean` เป็นชนิดข้อมูลบูลีนในตัว ส่วนหัวของไฟล์stdbool.hมีมาโคร `boolean`bool, `boolean`trueและ `boolean`falseที่ถูกกำหนดเป็น ` boolean`_Bool, `boolean`1และ0`boolean` ตามลำดับ ดังนั้น ` boolean`trueและ `boolean` จึงfalseมีชนิดข้อมูลเป็นint` boolean` - อย่างไรก็ตามในC23
bool(และค่าต่างๆ ของมันtrue)falseถือเป็นคำสำคัญ
- ในภาษา C++
- C++ มีชนิดข้อมูล
char8_t,char16_tและchar32_tเพื่อเข้ารหัส หน่วยรหัส UTF เดียว C23 ก็มีชนิดข้อมูลเหล่านี้เช่นกัน แต่เป็นการกำหนดชนิดข้อมูลให้กับชนิดข้อมูลจำนวนเต็มอื่นๆ แทนที่จะเป็นชนิดข้อมูลในตัวที่แตกต่างกัน ดังนั้นชื่อของชนิดข้อมูลเหล่านี้จึงไม่ใช่คำสงวนใน<uchar.h>. - ในภาษา C นั้น การกำหนดว่าฟิลด์บิตของชนิดข้อมูล ใด
intจะเป็นแบบมีเครื่องหมายหรือไม่มีเครื่องหมายขึ้นอยู่กับการใช้งาน ในขณะที่ในภาษา C++ นั้น ฟิลด์บิตจะเป็นแบบมีเครื่องหมายเสมอเพื่อให้ตรงกับชนิดข้อมูลพื้นฐาน
ความแตกต่างอื่นๆ จากหัวข้อก่อนหน้านี้หลายประการ สามารถนำมาใช้สร้างโค้ดที่คอมไพล์ได้ทั้งสองภาษา แต่มีพฤติกรรมที่แตกต่างกันได้ ตัวอย่างเช่น ฟังก์ชันต่อไปนี้จะส่งคืนค่าที่แตกต่างกันในภาษา C และ C++:
ภายนอกภายในT ;int size ( void ) { struct T { long i , j ; }; return sizeof ( T ); // C: return sizeof(int) // C++: return sizeof(struct T) }นี่เป็นเพราะภาษา C กำหนดให้ต้องมีstructเครื่องหมาย `<br>` อยู่หน้าแท็กโครงสร้าง (และจึงsizeof(T)หมายถึงตัวแปร) แต่ภาษา C++ อนุญาตให้ละเว้นได้ (และจึงsizeof(T)หมายถึงค่าโดยปริยายtypedef) โปรดระวังว่าผลลัพธ์จะแตกต่างกันเมื่อexternประกาศตัวแปรไว้ภายในฟังก์ชัน: ในกรณีนั้น การมีตัวระบุที่มีชื่อเดียวกันในขอบเขตของฟังก์ชันจะยับยั้งไม่ให้ค่าโดยปริยายtypedefมีผลในภาษา C++ และผลลัพธ์สำหรับภาษา C และ C++ จะเหมือนกัน นอกจากนี้ โปรดสังเกตว่าความกำกวมในตัวอย่างข้างต้นเกิดจากการใช้วงเล็บกับsizeofตัวดำเนินการ `<br>` การใช้ `<br> sizeof T` จะคาดหวังว่าTจะเป็นนิพจน์ไม่ใช่ชนิดข้อมูล ดังนั้นตัวอย่างจึงไม่สามารถคอมไพล์ได้ในภาษา C++
การเชื่อมโยงโค้ด C และ C++
แม้ว่าภาษา C และ C++ จะมีความเข้ากันได้ของซอร์สโค้ดในระดับสูง แต่ไฟล์ออบเจ็กต์ที่คอมไพเลอร์ของแต่ละภาษาสร้างขึ้นอาจมีความแตกต่างที่สำคัญ ซึ่งจะปรากฏให้เห็นเมื่อมีการผสมผสานโค้ด C และ C++ เข้าด้วยกัน โดยเฉพาะอย่างยิ่ง:
- คอมไพเลอร์ C ไม่ตั้งชื่อสัญลักษณ์ mangleในลักษณะเดียวกับที่คอมไพเลอร์ C++ ทำ[ 24 ]
- ขึ้นอยู่กับคอมไพเลอร์และสถาปัตยกรรม อาจมีความเป็นไปได้ที่ข้อกำหนดในการเรียกใช้ฟังก์ชันจะแตกต่างกันระหว่างสองภาษา
ด้วยเหตุผลเหล่านี้ ในการที่โค้ด C++ จะเรียกใช้ฟังก์ชัน C foo()นั้น โค้ด C++ จะต้องประกาศต้นแบบfoo()ด้วย `prototype` extern "C"ในทำนองเดียวกัน ในการที่โค้ด C จะเรียกใช้ฟังก์ชัน C++ bar()นั้น โค้ด C++ สำหรับฟังก์ชันนั้นbar()จะต้องประกาศด้วยextern "C"` prototype`
แนวปฏิบัติทั่วไปสำหรับไฟล์ส่วนหัวเพื่อรักษาความเข้ากันได้ทั้ง C และ C++ คือการทำให้การประกาศเป็นไปextern "C"ในขอบเขตของส่วนหัว: [ 25 ]
ในfoo.h :
// ถ้าเป็นคอมไพเลอร์ C++ ให้ใช้การเชื่อมโยง C #ifdef __cplusplus extern "C" { #endif// สัญลักษณ์ใดๆ ที่ประกาศไว้ที่นี่จะได้รับการเชื่อมโยงแบบ C void foo (); int bar ( int a , int b );// ถ้าเป็นคอมไพเลอร์ C++ ให้จบการเชื่อมโยง C #ifdef __cplusplus } #endifความแตกต่างระหว่าง การเชื่อมโยง และการเรียกใช้ใน ภาษา C และ C++ อาจมีนัยสำคัญเล็กน้อยสำหรับโค้ดที่ใช้ตัวชี้ฟังก์ชันคอมไพเลอร์บางตัวจะสร้างโค้ดที่ไม่ทำงานหากตัวชี้ฟังก์ชันที่ประกาศไว้extern "C"ชี้ไปยังฟังก์ชัน C++ ที่ไม่ได้extern "C"ประกาศ[ 26 ]
ตัวอย่างเช่น โค้ดต่อไปนี้:
void qux ();extern "C" void foo ( void ( * fp )( void ));void bar () {foo ( qux );}เมื่อใช้ คอมไพเลอร์ C++ ของ Sun Microsystemsอาจทำให้เกิดคำเตือนต่อไปนี้:
"test.cpp", บรรทัดที่ 5: คำเตือน (Anachronism): อาร์กิวเมนต์อย่างเป็นทางการ fp ประเภท extern "C" void(*)() ในการเรียก foo(extern "C" void(*)()) กำลังถูกส่งผ่านเป็นvoid(*)()เนื่องจากตัวแปรqux()ไม่ได้ถูกประกาศโดยใช้การเชื่อมโยงและการเรียกใช้ตามหลักการของภาษา C แต่ถูกส่งผ่านไปยังฟังก์ชันของภาษา C foo()แทน
ดูเพิ่มเติม
ลิงก์ภายนอก
- การเปรียบเทียบโดยละเอียดทีละประโยค จากมุมมองของมาตรฐาน C89
- ความไม่เข้ากันระหว่าง ISO C และ ISO C++โดย David R. Tribble (สิงหาคม 2544)
- คู่มือการย้ายระบบ C++ ของ Oracle (Sun Microsystems) ส่วนที่ 3.11เอกสารประกอบคอมไพเลอร์ของ Oracle/Sun เกี่ยวกับขอบเขตการเชื่อมโยง (linkage scope)
- Oracle: การผสมผสานโค้ด C และ C++ ในโปรแกรมเดียวกันภาพรวมโดย Steve Clamage (ประธานคณะกรรมการ ANSI C++)