คุณสมบัติที่ซ่อนอยู่ของ C


141

ฉันรู้ว่ามีมาตรฐานอยู่เบื้องหลังการใช้งานคอมไพเลอร์ C ทั้งหมดดังนั้นจึงไม่มีคุณสมบัติที่ซ่อนอยู่ แม้จะเป็นเช่นนั้นฉันมั่นใจว่านักพัฒนา C ทุกคนมีกลอุบายที่ซ่อนอยู่ / ความลับที่พวกเขาใช้ตลอดเวลา


มันจะดีถ้าคุณ / ใครบางคนต้องแก้ไข“ คำถาม” เพื่อระบุตัวเลือกของคุณสมบัติที่ซ่อนอยู่ที่ดีที่สุดเช่นในรุ่น C # และ Perl ของคำถามนี้
Donal Fellows

คำตอบ:


62

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

จากนั้นจะมีอัญมณีที่ซ่อนอยู่ในไลบรารีมาตรฐานเช่น qsort (), bsearch (), strpbrk (), strcspn () [สองหลังหลังมีประโยชน์สำหรับการนำ Strtok () มาใช้แทน)

misfeature ของ C คือ overflow เลขคณิตที่ลงนามแล้วนั้นเป็นพฤติกรรมที่ไม่ได้กำหนด (UB) ดังนั้นเมื่อใดก็ตามที่คุณเห็นนิพจน์เช่น x + y ทั้งคู่มีการลงชื่อ int ก็อาจเกิดการล้นและทำให้เกิด UB


29
แต่ถ้าพวกเขามีพฤติกรรมที่ระบุเกี่ยวกับการล้นมันจะทำให้ช้ามากในสถาปัตยกรรมที่ไม่ใช่พฤติกรรมปกติ ค่าใช้จ่ายในการรันไทม์ต่ำมากนั้นเป็นเป้าหมายการออกแบบของ C เสมอและนั่นหมายความว่าสิ่งต่าง ๆ มากมายเช่นนี้ไม่ได้กำหนด
Mark Baker

9
ฉันรู้ดีว่าทำไมล้นเป็น UB มันยังคงเป็นความผิดพลาดเนื่องจากมาตรฐานควรมีไลบรารีรูทีนที่เตรียมไว้อย่างน้อยที่สามารถทดสอบการโอเวอร์โฟลว์ทางคณิตศาสตร์ (ของการดำเนินการพื้นฐานทั้งหมด) โดยไม่มี UB
zvrba

2
@zvrba, "ไลบรารีรูทีนที่สามารถทดสอบการคำนวณทางคณิตศาสตร์ล้น (ของการดำเนินการพื้นฐานทั้งหมด)" หากคุณเพิ่มสิ่งนี้แล้วคุณจะได้รับผลการทำงานที่สำคัญสำหรับการดำเนินการทางคณิตศาสตร์จำนวนเต็มใด ๆ ===== กรณีศึกษา Matlab โดยเฉพาะเพิ่มคุณสมบัติของการควบคุมพฤติกรรมล้นจำนวนเต็มเพื่อห่อหรืออิ่มตัว และมันยังส่งข้อยกเว้นเมื่อใดก็ตามที่มีการล้นเกิดขึ้น ==> ประสิทธิภาพของการดำเนินการจำนวนเต็ม Matlab: VERY SLOW ข้อสรุปของฉันเอง: ฉันคิดว่า Matlab เป็นกรณีศึกษาที่น่าสนใจซึ่งแสดงให้เห็นว่าทำไมคุณไม่ต้องการตรวจสอบจำนวนเต็มล้น
Trevor Boyd Smith

15
ฉันบอกว่ามาตรฐานควรให้การสนับสนุนห้องสมุดสำหรับการตรวจสอบการคำนวณทางคณิตศาสตร์มากเกินไป ทีนี้รูทีนไลบรารีอาจทำให้ประสิทธิภาพลดลงได้อย่างไรถ้าคุณไม่เคยใช้
zvrba

5
ข้อเสียใหญ่คือ GCC ไม่มีแฟล็กที่จะจับจำนวนเต็มล้นที่มีลายเซ็นและโยนข้อยกเว้นรันไทม์ แม้ว่าจะมีแฟล็ก x86 สำหรับตรวจจับกรณีดังกล่าว แต่ GCC ก็ไม่ได้ใช้ประโยชน์ การมีการตั้งค่าสถานะดังกล่าวจะช่วยให้แอปพลิเคชั่นที่ไม่สำคัญ (โดยเฉพาะรุ่นเก่า) ได้รับประโยชน์จากการรักษาความปลอดภัยโดยมีการตรวจสอบรหัสและการปรับโครงสร้างใหม่ให้น้อยที่สุด
Andrew Keeton

116

เคล็ดลับเพิ่มเติมเกี่ยวกับคอมไพเลอร์ GCC แต่คุณสามารถให้คำแนะนำการบ่งชี้สาขาแก่คอมไพเลอร์ (พบได้ทั่วไปในเคอร์เนล Linux)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

ดู: http://kerneltrap.org/node/4705

สิ่งที่ฉันชอบเกี่ยวกับเรื่องนี้คือมันยังเพิ่มความหมายบางอย่างให้กับฟังก์ชั่นบางอย่าง

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
เคล็ดลับนี้เจ๋ง ... :) โดยเฉพาะอย่างยิ่งกับมาโครที่คุณกำหนด :)
sundar - Reinstate Monica

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

สิ่งเหล่านี้เป็นรายการทางเลือกในมาตรฐาน แต่ต้องเป็นคุณลักษณะที่ซ่อนอยู่เนื่องจากผู้คนกำลังกำหนดใหม่อยู่ตลอดเวลา ฐานรหัสเดียวที่ฉันได้ทำไป (และยังทำอยู่ตอนนี้) มีการกำหนดซ้ำหลายครั้งทั้งหมดมีตัวระบุที่แตกต่างกัน ส่วนใหญ่แล้วมาพร้อมกับมาโครตัวประมวลผลล่วงหน้า:

#define INT16 short
#define INT32  long

และอื่น ๆ มันทำให้ฉันต้องการดึงผมออกมา เพียงใช้ typedefs จำนวนเต็มมาตรฐานที่บ้าแล้ว!


3
ฉันคิดว่าพวกเขาเป็น C99 หรือมากกว่านั้น ฉันไม่พบวิธีพกพาเพื่อให้แน่ใจว่าสิ่งเหล่านี้จะอยู่ใกล้
akauppi

3
พวกเขาเป็นส่วนเสริมของ C99 แต่ฉันรู้ว่าไม่มีผู้ขายคอมไพเลอร์ที่ไม่ใช้สิ่งนี้
Ben Collins

10
stdint.h ไม่ได้เป็นตัวเลือกใน C99 แต่ต่อไปนี้มาตรฐาน C99 เห็นได้ชัดสำหรับผู้ขายบางคน ( ไอ Microsoft)
Ben Combee

5
@Pete หากคุณต้องการเป็นคนที่ชอบทวารหนัก: (1) เธรดนี้ไม่มีส่วนเกี่ยวข้องกับผลิตภัณฑ์ Microsoft ใด ๆ (2) เธรดนี้ไม่มีส่วนเกี่ยวข้องกับ C ++ เลย (3) ไม่มีสิ่งเช่น C ++ 97
Ben Collins

5
ดูazillionmonkeys.com/qed/pstdint.h - stdint.h แบบพกพาได้ใกล้กับมือถือ
gnud

73

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

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

แต่คุณสามารถใช้โอเปอเรเตอร์นี้ได้ทุกที่ สังเกต:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

แต่ละคำสั่งถูกประเมิน แต่ค่าของนิพจน์จะเป็นของคำสั่งสุดท้ายที่ประเมิน


7
ใน 20 ปีของ CI ไม่เคยเห็นแบบนั้นมาก่อน!
Martin Beckett

11
ใน C ++ คุณสามารถโอเวอร์โหลดได้
Wouter Lievens

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

int ภายในลูปจะไม่ทำงานกับ C: มันเป็นการปรับปรุง C ++ เป็นการดำเนินการ "," เหมือนกับ (i = 0, j = 10; i <j; j--, i ++) หรือไม่
Aif

63

เริ่มต้นโครงสร้างเป็นศูนย์

struct mystruct a = {0};

สิ่งนี้จะเป็นศูนย์องค์ประกอบโครงสร้างทั้งหมด


2
อย่างไรก็ตามมันไม่ได้เป็นศูนย์ถ้ามี
Mikeage

2
@simonn ไม่ว่าจะเป็นพฤติกรรมที่ไม่ได้กำหนดหากโครงสร้างมีประเภทที่ไม่ครบถ้วน memset ที่มี 0 ในหน่วยความจำของ float / double จะยังคงเป็นศูนย์เมื่อคุณตีความ float / double (float / double ได้รับการออกแบบเช่นนั้นตามวัตถุประสงค์)
Trevor Boyd Smith

6
@Andrew: memset/ callocdo "all bytes zero" (เช่นศูนย์ทางกายภาพ) ซึ่งแน่นอนไม่ได้กำหนดไว้สำหรับทุกประเภท { 0 } รับประกันได้ว่าจะทำให้ทุกอย่างลงตัวด้วยค่าศูนย์ตรรกะที่เหมาะสม ตัวชี้ตัวอย่างเช่นมีการค้ำประกันที่จะได้รับค่า null 0xBAADFOODที่เหมาะสมของพวกเขาแม้ว่าโมฆะค่าบนแพลตฟอร์มที่ได้รับคือ
AnT

1
@nvl: คุณได้รับศูนย์ทางกายภาพเมื่อคุณเพียงแค่ตั้งค่าหน่วยความจำทั้งหมดที่ครอบครองโดยวัตถุให้เป็นสถานะ all-bits-zero นี่คือสิ่งที่memsetทำ (ด้วย0อาร์กิวเมนต์ที่สอง) คุณได้รับศูนย์ตรรกะเมื่อคุณเริ่มต้น / กำหนด0(หรือ{ 0 }) ไปยังวัตถุในรหัสที่มา ศูนย์สองชนิดนี้ไม่จำเป็นต้องให้ผลลัพธ์เหมือนกัน เช่นเดียวกับในตัวอย่างด้วยตัวชี้ เมื่อคุณทำmemsetกับตัวชี้คุณจะได้รับ0x0000ตัวชี้ แต่เมื่อคุณกำหนดให้0กับตัวชี้คุณจะได้รับค่าตัวชี้ nullซึ่งในระดับกายภาพอาจเป็น0xBAADF00Dหรือสิ่งอื่นใด
AnT

3
@nvl: ในทางปฏิบัติความแตกต่างมักจะเป็นเพียงแนวคิด แต่ในทางทฤษฎีแทบทุกประเภทสามารถมีได้ ตัวอย่างเช่นdouble. โดยปกติจะมีการใช้งานตามมาตรฐาน IEEE-754 ซึ่งเป็นศูนย์ตรรกะและศูนย์ทางกายภาพเหมือนกัน แต่ภาษานั้นไม่จำเป็นต้องใช้ IEEE-754 ดังนั้นจึงอาจเกิดขึ้นได้เมื่อคุณทำdouble d = 0;(โลจิคัลศูนย์) ฟิสิคัลบิตบางส่วนในหน่วยความจำที่ครอบครองโดยdจะไม่เป็นศูนย์
AnT

52

ค่าคงที่แบบหลายตัวอักษร:

int x = 'ABCD';

ชุดนี้xเป็น0x41424344(หรือ0x44434241ขึ้นอยู่กับสถาปัตยกรรม)

แก้ไข:เทคนิคนี้ไม่ได้พกพาโดยเฉพาะอย่างยิ่งถ้าคุณเป็นอนุกรม int อย่างไรก็ตามมันมีประโยชน์อย่างยิ่งในการสร้าง enums เอกสารด้วยตนเอง เช่น

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

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


ฉันค่อนข้างแน่ใจว่านี่ไม่ใช่โครงสร้างแบบพกพา ผลลัพธ์ของการสร้างค่าคงที่แบบหลายอักขระกำหนดโดยการนำไปปฏิบัติ
Mark Bessey

8
ความคิดเห็นที่ "ไม่พกพา" พลาดจุดนี้อย่างสิ้นเชิง มันก็เหมือนกับการวิพากษ์วิจารณ์โปรแกรมสำหรับการใช้ INT_MAX เพียงเพราะ INT_MAX เป็น "ไม่พกพา" :) คุณสมบัตินี้เป็นแบบพกพาเท่าที่จำเป็น ค่าคงที่แบบ Multi-char เป็นคุณสมบัติที่มีประโยชน์อย่างยิ่งที่ให้วิธีการที่สามารถอ่านได้เพื่อสร้าง ID จำนวนเต็มเฉพาะ
AnT

1
@Chris Lutz - ฉันค่อนข้างแน่ใจว่าเครื่องหมายจุลภาคต่อท้ายกลับไปที่ K&R มันอธิบายไว้ในรุ่นที่สอง (1988)
Ferruccio

1
@Ferruccio: คุณต้องนึกถึงคอมมาต่อท้ายในรายการเริ่มต้นรวม สำหรับเครื่องหมายจุลภาคต่อท้ายในการประกาศ enum - มันเป็นการเพิ่มล่าสุด, C99
AnT

3
คุณลืม 'HANG' หรือ 'BSOD' :-)
JBRWilkinson

44

ฉันไม่เคยใช้บิตฟิลด์แต่มันฟังดูเจ๋งสำหรับสิ่งที่ต่ำมาก

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

ซึ่งหมายความว่าสามารถมีขนาดเล็กเป็นsizeof(cat)sizeof(char)


ความคิดเห็นรวมโดยแอรอนและleppieขอบคุณครับ


การรวมกันของ structs และ unions น่าสนใจยิ่งขึ้น - ในระบบฝังตัวหรือรหัสไดรเวอร์ระดับต่ำ ตัวอย่างคือเมื่อคุณต้องการแยกการลงทะเบียนของการ์ด SD คุณสามารถอ่านได้โดยใช้ยูเนี่ยน (1) และอ่านมันโดยใช้ยูเนี่ยน (2) ซึ่งเป็นโครงสร้างของบิตฟิลด์
ComSubVie

5
Bitfields ไม่ใช่แบบพกพาคอมไพเลอร์สามารถเลือกได้อย่างอิสระไม่ว่าในตัวอย่างของคุณขาจะได้รับการจัดสรร 3 บิตที่สำคัญที่สุดหรือ 3 บิตที่สำคัญน้อยที่สุด
zvrba

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

26
Bitfields สามารถพกพาได้จริงตราบใดที่คุณถือว่ามันเป็นองค์ประกอบโครงสร้างและไม่ใช่ "ส่วนของจำนวนเต็ม" ขนาดไม่ใช่ตำแหน่งสำคัญในระบบฝังตัวที่มีหน่วยความจำ จำกัด เนื่องจากแต่ละบิตมีค่า ... แต่ตัวเข้ารหัสส่วนใหญ่ในวันนี้ยังเด็กเกินไปที่จะจำได้ :-)
Adam Liss

5
@ Adam: ตำแหน่งอาจมีความสำคัญในระบบฝังตัว (หรือที่อื่น ๆ ) หากคุณขึ้นอยู่กับตำแหน่งของบิตฟิลด์ภายในไบต์ การใช้มาสก์จะกำจัดความกำกวมใด ๆ ในทำนองเดียวกันสำหรับสหภาพ
Steve Melnikoff

37

C มีคอมไพเลอร์มาตรฐาน แต่คอมไพเลอร์ C ทั้งหมดนั้นไม่ได้มาตรฐานอย่างสมบูรณ์ (ฉันยังไม่เห็นคอมไพเลอร์ C99 ที่เข้ากันได้เลย!)

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

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

...
a ^= b ; b ^= a; a ^=b;
...

หรือ "การขยาย C" เพื่อแทนเครื่องสถานะ จำกัด เช่น:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

ที่สามารถทำได้ด้วยมาโครต่อไปนี้:

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

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


18
C สนับสนุนการโยงดังนั้นคุณสามารถทำ ^ = b ^ = a ^ = b;
OJ

4
การพูดอย่างเคร่งครัดตัวอย่างสถานะเป็นเครื่องหมายของตัวประมวลผลล่วงหน้าและไม่ใช่ภาษา C - เป็นไปได้ที่จะใช้ตัวแรกโดยไม่มีตัวหลัง
Greg Whitfield

15
OJ: จริง ๆ แล้วสิ่งที่คุณแนะนำคือพฤติกรรมที่ไม่ได้กำหนดเนื่องจากกฎจุดลำดับ มันอาจทำงานกับคอมไพเลอร์ส่วนใหญ่ แต่ไม่ถูกต้องหรือพกพา
Evan Teran

5
Xor swap อาจมีประสิทธิภาพน้อยลงในกรณีของการลงทะเบียนฟรี เครื่องมือเพิ่มประสิทธิภาพที่เหมาะสมจะทำให้ตัวแปร temp เป็นรีจิสเตอร์ ขึ้นอยู่กับการนำไปใช้ (และต้องการการสนับสนุนแบบคู่ขนาน) การสลับอาจใช้หน่วยความจำจริงแทนการลงทะเบียน (ซึ่งจะเหมือนกัน)
Paul de Vrieze

27
โปรดอย่าทำสิ่งนี้จริง ๆ : en.wikipedia.org/wiki/…
Christian Oudard

37

โครงสร้างพัวพันเช่นอุปกรณ์ของ Duff :

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

29
@ComSubVie ทุกคนที่ใช้อุปกรณ์ของ Duff เป็น kiddy สคริปต์ที่เห็นอุปกรณ์ของ Duff และคิดว่ารหัสของพวกเขาจะมีลักษณะ 1337 ถ้าพวกเขาใช้อุปกรณ์ของ Duff (1. ) อุปกรณ์ของ Duff ไม่ได้เพิ่มประสิทธิภาพการทำงานใด ๆ บนโปรเซสเซอร์ที่ทันสมัยเนื่องจากโปรเซสเซอร์ที่ทันสมัยมีการวนรอบศูนย์ที่ไม่มีค่าใช้จ่าย มันเป็นโค้ดที่ล้าสมัย (2. ) แม้ว่าตัวประมวลผลของคุณจะไม่มีการวนรอบศูนย์เลย แต่ก็อาจมีบางอย่างเช่น SSE / altivec / การประมวลผลแบบเวกเตอร์ซึ่งจะทำให้อุปกรณ์ Duff ของคุณน่าละอายเมื่อคุณใช้ memcpy () (3. ) ฉันพูดถึงว่าคนอื่นที่ทำ memcpy () ของดัฟฟ์ไม่เป็นประโยชน์หรือไม่?
Trevor Boyd Smith

2
@ComSubVie โปรดพบกับกำปั้นแห่งความตายของฉัน ( en.wikipedia.org/wiki/ … )
Trevor Boyd Smith

12
@ เทรเวอร์: ดังนั้นเฉพาะโปรแกรม kiddies 8051 และไมโครคอนโทรลเลอร์ PIC ของตัวเองใช่ไหม?
เอสเอฟ

6
@ เทรเวอร์บอยด์สมิ ธ : ในขณะที่อุปกรณ์ของดัฟฟ์ปรากฏว่าล้าสมัยมันยังคงเป็นความอยากรู้ทางประวัติศาสตร์ซึ่งตรวจสอบคำตอบของ ComSubVie อย่างไรก็ตามการอ้างถึงวิกิพีเดีย: "เมื่ออุปกรณ์ของ Duff จำนวนมากถูกลบออกจากเซิร์ฟเวอร์ XFree86 ในเวอร์ชัน 4.0 มีการปรับปรุงประสิทธิภาพการทำงานที่โดดเด่น" ...
paercebal

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

33

ฉันชอบ initializers ที่กำหนดเพิ่มใน C99 (และรองรับเป็น gcc เป็นเวลานาน):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

การกำหนดค่าเริ่มต้นของอาร์เรย์ไม่ขึ้นอยู่กับตำแหน่งอีกต่อไป หากคุณเปลี่ยนค่าของ FOO หรือ BAR การเริ่มต้นอาร์เรย์จะสอดคล้องกับค่าใหม่โดยอัตโนมัติ


ไวยากรณ์ gcc ได้รับการสนับสนุนเป็นเวลานานไม่เหมือนกับไวยากรณ์ C99 มาตรฐาน
Mark Baker

28

C99 มีโครงสร้างเริ่มต้นที่ยอดเยี่ยม

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}


27

โครงสร้างและอาร์เรย์ที่ไม่ระบุชื่อเป็นสิ่งที่ฉันโปรดปราน (cf. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

หรือ

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

มันยังสามารถใช้เพื่อสร้างรายการเชื่อมโยง ...


3
คุณสมบัตินี้มักจะเรียกว่า "ตัวอักษรผสม" โครงสร้างแบบไม่ระบุชื่อ (หรือไม่มีชื่อ) กำหนดโครงสร้างแบบซ้อนที่ไม่มีชื่อสมาชิก
calandoa

ตาม GCC ของฉัน "ISO C90 ห้ามการผสมตัวอักษร"
jmtd

"ISO C99 รองรับตัวอักษรผสม" "ในฐานะที่เป็นส่วนเสริม GCC รองรับตัวอักษรผสมในโหมด C89 และใน C ++" (dixit info gcc) นอกจากนี้ "ในฐานะที่เป็นส่วนขยาย GNU GCC อนุญาตให้เริ่มต้นวัตถุด้วยระยะเวลาการจัดเก็บแบบคงที่ด้วยตัวอักษรผสม (ซึ่งเป็นไปไม่ได้ใน ISO C99 เนื่องจากตัวเริ่มต้นไม่ใช่ค่าคงที่)"
PypeBros

24

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

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));

24

คุณสมบัติ (ซ่อนอยู่) ที่ "ทำให้ตกใจ" เมื่อฉันเห็นครั้งแรกเกี่ยวกับ printf คุณสมบัตินี้ช่วยให้คุณใช้ตัวแปรสำหรับการจัดรูปแบบตัวระบุรูปแบบเอง มองหารหัสคุณจะเห็นดีกว่า:

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

อักขระ * บรรลุเอฟเฟกต์นี้


24

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


แต่ในความเป็นจริงแล้วคุณต้องคอมไพล์โค้ดด้วยคอมไพเลอร์ตัวอื่นบ่อยแค่ไหน?
Joe D

3
@ Joe D ถ้าโครงการแพลตฟอร์มข้ามเช่น Windows / OSX / Linux อาจบิตและยังมีที่แตกต่างกันเช่นซุ้ม x86 VS x86_64 และ ฯลฯ ...
Pharaun

@ JoeD เว้นแต่คุณจะอยู่ในโครงการที่แคบมาก ๆ ซึ่งยินดีที่จะแต่งงานกับผู้ขายคอมไพเลอร์รายหนึ่งมาก คุณอาจต้องการหลีกเลี่ยงการเปลี่ยนคอมไพเลอร์ แต่คุณต้องการให้ตัวเลือกนั้นเปิดอยู่ ด้วยระบบฝังตัวคุณไม่ได้รับตัวเลือกเสมอไป AHS, ASS
XTL

19

ยืนยันเวลารวบรวมเป็นที่กล่าวถึงอยู่ที่นี่แล้ว

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);

16

การต่อสตริงอย่างต่อเนื่อง

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

ใช้กรณีที่ฉันมีในรหัสปัจจุบันของฉัน: ฉันมี#define PATH "/some/path/"ในไฟล์การกำหนดค่า (จริงๆมันถูก setted โดย makefile) ตอนนี้ฉันต้องการสร้างเส้นทางแบบเต็มรวมถึงชื่อไฟล์เพื่อเปิดแหล่งข้อมูล มันไปที่:

fd = open(PATH "/file", flags);

แทนที่จะน่ากลัว แต่เป็นเรื่องธรรมดามาก:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

ขอให้สังเกตว่าทางออกที่น่ากลัวทั่วไปคือ:

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

1
นอกจากนี้ยังมีประโยชน์ในการแบ่งค่าคงที่สตริงในหลาย ๆ บรรทัดโดยไม่ใช้ `` `สกปรก
dolmen


12

เมื่อเริ่มต้นอาร์เรย์หรือ enums คุณสามารถใส่เครื่องหมายจุลภาคหลังรายการสุดท้ายในรายการ initializer เช่น:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

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


สิ่งนี้มีความสำคัญในสภาพแวดล้อมที่มีนักพัฒนาหลายคนเช่น Eric เพิ่มใน "baz" จากนั้น George เพิ่มใน "boom" หาก Eric ตัดสินใจที่จะดึงรหัสของเขาออกมาสำหรับการสร้างโครงการต่อไปมันก็ยังคงสอดคล้องกับการเปลี่ยนแปลงของ George สำคัญมากสำหรับการควบคุมซอร์สโค้ดแบบหลายสาขาและกำหนดการพัฒนาที่ทับซ้อนกัน
Harold Bamford

Enums อาจเป็น C99 อาร์เรย์เริ่มต้นและเครื่องหมายจุลภาคต่อท้ายคือ K&R
Ferruccio

enums ธรรมดาอยู่ใน c89, AFAIK อย่างน้อยพวกเขาก็อยู่แถวนี้มานานแล้ว
XTL

12

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

ตัวอย่างเช่นลองพิจารณาห้องสมุดกราฟิก 2D ในจินตนาการมันอาจกำหนดประเภทเพื่อแสดงพิกัดหน้าจอ (จำนวนเต็ม):

typedef struct {
   int x;
   int y;
} Point;

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

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

มีความปลอดภัยตราบเท่าที่ (แน่นอน) เนื่องจากค่าส่งคืนถูกคัดลอกโดยค่าโดยใช้การกำหนด struct:

Point origin;
origin = point_new(0, 0);

ด้วยวิธีนี้คุณสามารถเขียนโค้ดที่ค่อนข้างสะอาดและ object-oriented-ish ทั้งหมดนี้เป็นมาตรฐานธรรมดา C


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

1
แน่นอนว่าอาจจะมี คอมไพเลอร์ตรวจจับการใช้งานและปรับให้เหมาะสม
คลาย

ระวังหากองค์ประกอบใด ๆ เป็นพอยน์เตอร์เนื่องจากคุณจะคัดลอกพอยน์เตอร์เองไม่ใช่เนื้อหา แน่นอนเช่นเดียวกันหากคุณใช้ memcpy ()
Adam Liss

คอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการแปลงนี้โดยการส่งผ่านค่าด้วย by-referenece เว้นแต่ว่ามันจะสามารถเพิ่มประสิทธิภาพระดับโลก
Blaisorblade

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

10

การจัดทำดัชนีเวกเตอร์แปลก ๆ :

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

4
ดียิ่งขึ้นกว่านี้ ... ถ่าน c = 2 ["Hello"]; (c == 'l' หลังจากนี้)
yrp

5
ไม่แปลกมากเมื่อคุณพิจารณา v [index] == * (v + index) และ index [v] == * (index + v)
Ferruccio

17
โปรดบอกฉันว่าคุณไม่ได้ใช้ "ตลอดเวลา" นี้เหมือนคำถามที่ถาม!
Tryke

9

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

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

เทคนิคต่าง ๆ ที่มีรหัส C แตกรวมถึง:

  1. ขึ้นอยู่กับวิธีที่คอมไพเลอร์วางโครงสร้างในหน่วยความจำ
  2. สมมติฐานเกี่ยวกับendiannessของจำนวนเต็ม / ลอย
  3. สมมติฐานเกี่ยวกับฟังก์ชัน ABIs
  4. สมมติฐานเกี่ยวกับทิศทางที่กรอบสแต็คเติบโต
  5. ข้อสมมติฐานเกี่ยวกับลำดับของการดำเนินการภายในข้อความสั่ง
  6. ข้อสมมติฐานเกี่ยวกับลำดับของการดำเนินการของข้อความสั่งในฟังก์ชันอาร์กิวเมนต์
  7. สมมติฐานเกี่ยวกับขนาดบิตหรือความแม่นยำของชนิดสั้น, int, long, float และ double

ปัญหาและปัญหาอื่น ๆ ที่เกิดขึ้นเมื่อโปรแกรมเมอร์กำหนดสมมติฐานเกี่ยวกับแบบจำลองการดำเนินการที่ระบุไว้ในมาตรฐาน C ส่วนใหญ่เป็นพฤติกรรม 'ขึ้นอยู่กับคอมไพเลอร์'


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

2
@Blaisorblade ยิ่งกว่านั้นให้ใช้การยืนยันเวลารวบรวมเพื่อจัดทำเอกสารสมมติฐานของคุณในแบบที่จะทำให้คอมไพล์ล้มเหลวบนแพลตฟอร์มที่พวกเขาถูกละเมิด
RBerteig

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

การตรวจสอบฟังก์ชัน ABI ควรได้รับการจัดการโดยชุดทดสอบ
dolmen

9

เมื่อใช้ sscanf คุณสามารถใช้% n เพื่อค้นหาตำแหน่งที่คุณควรอ่านต่อ:

sscanf ( string, "%d%n", &number, &length );
string += length;

เห็นได้ชัดว่าคุณไม่สามารถเพิ่มคำตอบอื่นได้ดังนั้นฉันจะรวมคำตอบที่สองที่นี่คุณสามารถใช้ "&&" และ "||" ตามเงื่อนไข:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

รหัสนี้จะส่งออก:

สวัสดี
ROFL

8

การใช้ INT (3) เพื่อกำหนดจุดพักที่รหัสคือสิ่งที่ฉันโปรดปรานตลอดเวลา


3
ฉันไม่คิดว่ามันพกพาได้ มันจะใช้งานได้กับ x86 แต่แล้วแพลตฟอร์มอื่น ๆ ล่ะ?
Cristian Ciupitu

1
ฉันไม่มีความคิด - คุณควรโพสต์คำถามเกี่ยวกับมัน
Dror Helper

2
เป็นเทคนิคที่ดีและเป็น X86 เฉพาะ (แม้ว่าอาจมีเทคนิคที่คล้ายคลึงกันในแพลตฟอร์มอื่น ๆ ) อย่างไรก็ตามนี่ไม่ใช่คุณสมบัติของ C ขึ้นอยู่กับการขยาย C หรือการเรียกไลบรารี่ที่ไม่ได้มาตรฐาน
Ferruccio

1
ใน GCC มี __builtin_trap และสำหรับ MSVC __debugbreak ซึ่งจะทำงานกับสถาปัตยกรรมที่สนับสนุนใด ๆ
Axel Gneiting

8

คุณสมบัติ "ซ่อน" ที่ฉันโปรดปรานของ C คือการใช้% n ใน printf เพื่อเขียนกลับไปที่สแต็ก โดยปกติ printf จะแสดงค่าพารามิเตอร์จากสแต็กตามสตริงรูปแบบ แต่% n สามารถเขียนกลับมาได้

ตรวจสอบส่วน 3.4.2 ที่นี่ สามารถนำไปสู่ช่องโหว่ที่น่ารังเกียจมากมาย


ลิงก์ไม่ทำงานอีกต่อไปในความเป็นจริงไซต์ดูเหมือนว่าไม่ทำงาน คุณสามารถให้ลิงค์อื่นได้หรือไม่
thequark

@thequark: บทความใด ๆ ใน "ช่องโหว่รูปแบบสตริง" จะมีข้อมูลอยู่ในนั้น .. (เช่นcrypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ) .. อย่างไรก็ตามเนื่องจากลักษณะของฟิลด์ความปลอดภัย เว็บไซต์ตัวเองค่อนข้างสั่นคลอนและบทความทางวิชาการที่แท้จริงนั้นยากที่จะเกิดขึ้น
Sridhar Iyer

8

การรวบรวมเวลาตรวจสอบสมมติฐานโดยใช้ enums: ตัวอย่างโง่ ๆ แต่มีประโยชน์จริง ๆ สำหรับไลบรารีที่มีค่าคงที่ที่กำหนดเวลารวบรวมได้

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};

2
+1 เรียบร้อย ฉันเคยใช้มาโคร CompilerAssert จาก Microsoft แต่คุณก็ไม่ได้แย่เหมือนกัน ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter

1
ฉันชอบวิธีการแจงนับ วิธีที่ฉันใช้ก่อนใช้ประโยชน์จากการกำจัดรหัสที่ตายแล้ว: "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" ซึ่งไม่ได้มีข้อผิดพลาดจนกว่าจะถึงเวลาลิงก์ โปรแกรมเมอร์ทราบผ่านข้อความแสดงข้อผิดพลาดว่า blorg เป็น woozled
supercat

8

Gcc (c) มีฟีเจอร์สนุก ๆ ที่คุณสามารถเปิดใช้งานได้เช่นการประกาศฟังก์ชันที่ซ้อนกันและรูปแบบ?: b ของตัวดำเนินการ?: ซึ่งจะส่งกลับ a ถ้าไม่ใช่เท็จ


8

ฉันค้นพบ 0 bitfields เร็ว ๆ นี้

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

ซึ่งจะให้รูปแบบของ

000aaabb 0ccccddd

แทนที่จะเป็น: 0;

0000aaab bccccddd

ฟิลด์ความกว้าง 0 บอกว่าบิตฟิลด์ต่อไปนี้ควรถูกตั้งค่าบนเอนทิตีอะตอมถัดไป (char )


7

แมโครอาร์กิวเมนต์ตัวแปร C99 สไตล์หรือที่รู้จัก

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

ซึ่งจะใช้เช่น

ERR(errCantOpen, "File %s cannot be opened", filename);

ที่นี่ฉันยังใช้ตัวดำเนินการ stringize และ string concatentation อย่างต่อเนื่องคุณสมบัติอื่น ๆ ที่ฉันชอบ


คุณมีพิเศษ 'R' ในVA_ARGS
Blaisorblade

6

ตัวแปรอัตโนมัติขนาดตัวแปรยังมีประโยชน์ในบางกรณี เหล่านี้ถูกเพิ่มเข้ามาฉัน nC99 และได้รับการสนับสนุนใน gcc เป็นเวลานาน

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

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

คุณต้องตรวจสอบให้แน่ใจว่า extraPadding เป็นค่าที่เหมาะสมก่อนที่จะเรียกรูทีนนี้หรือคุณจบการสแต็ก คุณต้องมีสติตรวจสอบข้อโต้แย้งก่อนโทร malloc หรือเทคนิคการจัดสรรหน่วยความจำอื่น ๆ ดังนั้นจึงไม่ผิดปกติ


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