เคล็ดลับการเขียนโปรแกรม C ที่คุณชอบคืออะไร? [ปิด]


134

ตัวอย่างเช่นฉันเพิ่งเจอสิ่งนี้ในเคอร์เนล linux:

/ * บังคับให้เกิดข้อผิดพลาดในการคอมไพล์หากเงื่อนไขเป็นจริง * /
#define BUILD_BUG_ON (เงื่อนไข) ((โมฆะ) sizeof (ถ่าน [1 - 2 * !! (เงื่อนไข)]))

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

BUILD_BUG_ON ((sizeof (โครงสร้างของโครงสร้าง)% 8)! = 0);

และจะไม่คอมไพล์เว้นแต่ขนาดของ struct mystruct จะเป็นผลคูณของ 8 และถ้าเป็นผลคูณของ 8 จะไม่มีการสร้างโค้ดรันไทม์เลย

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

#ifdef DEFINE_MYHEADER_GLOBALS
# กำหนดทั่วโลก
# กำหนด INIT (x, y) (x) = (y)
#อื่น
#define GLOBAL extern
# กำหนด INIT (x, y)
#endif

GLOBAL int INIT (x, 0);
GLOBAL int somefunc (int a, int b);

ด้วยเหตุนี้รหัสที่กำหนด x และ somefunc จึงทำดังนี้

# กำหนด DEFINE_MYHEADER_GLOBALS
# รวม "the_above_header_file.h"

ในขณะที่รหัสที่ใช้ x และ somefunc () ทำ:

# รวม "the_above_header_file.h"

ดังนั้นคุณจะได้รับไฟล์ส่วนหัวหนึ่งไฟล์ที่ประกาศทั้งอินสแตนซ์ของ globals และฟังก์ชันต้นแบบที่ต้องการและการประกาศภายนอกที่เกี่ยวข้อง

ดังนั้นเทคนิคการเขียนโปรแกรม C ที่คุณชื่นชอบในบรรทัดเหล่านี้คืออะไร?


9
ดูเหมือนว่าเทคนิคก่อนโปรเซสเซอร์ C
jmucchiello

เกี่ยวกับBUILD_BUG_ONมาโครมีอะไรผิดปกติกับการใช้#errorภายในและ#if?
Ricardo

คำตอบ:


80

C99 นำเสนอสิ่งที่ยอดเยี่ยมโดยใช้อาร์เรย์ที่ไม่ระบุชื่อ:

การลบตัวแปรที่ไม่มีจุดหมาย

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

กลายเป็น

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

การส่งผ่านจำนวนอาร์กิวเมนต์ที่แปรผัน

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

รายการที่เชื่อมโยงแบบคงที่

int main() {
    struct llist { int a; struct llist* next;};
    #define cons(x,y) (struct llist[]){{x,y}}
    struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL))));
    struct llist *p = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

แต่ฉันมั่นใจว่าเทคนิคเจ๋ง ๆ อีกมากมายที่ฉันไม่เคยคิด


2
ฉันเชื่อว่าตัวอย่างแรกของคุณสามารถเขียนได้เช่น&(int){1}กันหากคุณต้องการทำให้ชัดเจนขึ้นเล็กน้อยว่าเจตนาของคุณที่นี่คืออะไร
Lily Ballard

67

ในขณะที่อ่านซอร์สโค้ด Quake 2 ฉันพบสิ่งนี้:

double normals[][] = {
  #include "normals.txt"
};

(ไม่มากก็น้อยฉันไม่มีรหัสที่สะดวกในการตรวจสอบตอนนี้)

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

ขอบคุณ John Carmack! xD


13
คุณไม่สามารถพูด carmack ในเธรดการเพิ่มประสิทธิภาพได้โดยไม่ต้องพูดถึง sqrt ผกผันที่รวดเร็วซึ่งอยู่ในแหล่งกำเนิดแผ่นดินไหว en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989

เขาเอา 0x5f3759df มาจากไหนตั้งแต่แรก?
RSH1

2
@RoryHarvey: จากสิ่งที่ฉันหาได้เมื่อมองขึ้นไปดูเหมือนว่ามันจะเป็นเชิงประจักษ์เท่านั้น การศึกษาบางชิ้น (ฉันจำไม่ได้ว่าเคยเห็นที่ไหน) แสดงให้เห็นว่ามันใกล้เคียงกับที่เหมาะสมที่สุด แต่ยังไม่เหมาะสมที่สุด ในทำนองเดียวกันดูเหมือนว่าสำหรับ 64 บิตจะมีการค้นพบค่ามากกว่าการคำนวณ
Matthieu M.

50

ฉันชอบใช้= {0};เพื่อเริ่มต้นโครงสร้างโดยไม่จำเป็นต้องเรียก memset

struct something X = {0};

สิ่งนี้จะเริ่มต้นสมาชิกทั้งหมดของโครงสร้าง (หรืออาร์เรย์) เป็นศูนย์ (แต่ไม่ใช่ไบต์ช่องว่างใด ๆ - ใช้ memset หากคุณต้องการให้เป็นศูนย์ด้วยเช่นกัน)

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


ไม่จำเป็นสำหรับตัวแปรส่วนกลาง แต่อย่างใด
strager

5
ไม่จำเป็นสำหรับตัวแปรคงที่ ตัวแปรส่วนกลางอาจเป็นศูนย์ แต่ไม่ใช่ข้อกำหนด
Jamie

4
บางครั้งฉันขยายสิ่งนี้เป็น: const struct something zero_something = { 0 };จากนั้นฉันสามารถรีเซ็ตตัวแปรได้ทันทีโดยมีstruct something X = zero_something;หรือแยกทางผ่านกิจวัตรที่ฉันสามารถใช้ 'X = zero_something;' สิ่งเดียวที่เป็นไปได้คือการคัดค้านคือการอ่านข้อมูลจากที่ไหนสักแห่ง วันนี้ 'memset ()' อาจเร็วกว่า - แต่ฉันชอบความชัดเจนของงานและยังสามารถใช้ค่าอื่นที่ไม่ใช่ศูนย์ในตัวเริ่มต้นได้ด้วย (และ memset () ตามด้วยการปรับแต่งสมาชิกแต่ละคน อาจช้ากว่าสำเนาธรรมดา)
Jonathan Leffler

45

หากเรากำลังพูดถึงเทคนิค c ที่ฉันชอบต้องเป็นอุปกรณ์ของ Duffสำหรับการปลดลูป! ฉันแค่รอโอกาสที่เหมาะสมที่จะมาให้ฉันใช้มันด้วยความโกรธ ...


4
ฉันเคยใช้ครั้งเดียวเพื่อสร้างประสิทธิภาพที่เพิ่มขึ้นที่วัดได้ แต่ทุกวันนี้มันไม่มีประโยชน์กับฮาร์ดแวร์จำนวนมาก โปรไฟล์เสมอ!
Dan Olson

6
ใช่คนประเภทที่ไม่เข้าใจบริบทอุปกรณ์ของ Duff ถูกสร้างขึ้นใน: "การอ่านโค้ด" จะไม่มีประโยชน์หากโค้ดไม่เร็วพอที่จะทำงาน อาจไม่มีคนที่ลดคะแนนให้คุณเคยต้องเขียนโค้ดสำหรับเรียลไทม์อย่างหนัก
Rob K

1
+1 ฉันจำเป็นต้องใช้อุปกรณ์ของ Duff สองสามครั้งจริงๆ ครั้งแรกเป็นการวนซ้ำที่โดยพื้นฐานแล้วก็แค่คัดลอกสิ่งของและทำการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ ระหว่างทาง มันเร็วกว่า memcpy () ธรรมดาในสถาปัตยกรรมนั้นมาก
Makis

3
ความโกรธจะมาจากเพื่อนร่วมงานและผู้สืบทอดที่ต้องรักษารหัสของคุณหลังจากคุณ
Jonathan Leffler

1
อย่างที่บอกฉันยังรอโอกาสที่เหมาะสม - แต่ยังไม่มีใครกวนใจฉันมากพอ ฉันเขียน C มาประมาณ 25 ปีแล้วฉันคิดว่าฉันเจออุปกรณ์ของ Duff ครั้งแรกในช่วงต้นทศวรรษที่ 90 และฉันยังไม่ต้องใช้มัน เนื่องจากคนอื่น ๆ แสดงความคิดเห็นว่าเคล็ดลับประเภทนี้มีประโยชน์น้อยลงเรื่อย ๆ ในขณะนี้เนื่องจากคอมไพเลอร์ได้รับการปรับปรุงให้ดีขึ้นในการเพิ่มประสิทธิภาพประเภทนี้
Jackson

42

ใช้ __FILE__และ__LINE__สำหรับการดีบัก

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);

6
ในคอมไพเลอร์บางตัวคุณจะได้รับFUNCTIONเช่นกัน
JBRWilkinson

11
__FUNCTION__เป็นเพียงนามแฝง__func__และ__func__อยู่ใน c99 ค่อนข้างสะดวก __PRETTY_FUNCTION__ใน C (GCC) เป็นเพียงนามแฝงอื่น__func__แต่ใน C ++ จะทำให้คุณได้รับลายเซ็นฟังก์ชันทั้งหมด
sklnd

FILE แสดงพา ธ แบบเต็มของชื่อไฟล์ดังนั้นฉันจึงใช้ basename ( FILE )
Jeegar Patel


28

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

สิ่งที่ต้องการ:

#define return DoSomeStackCheckStuff, return

4
หวังว่านั่นคือ # กำหนดในเนื้อความของฟังก์ชันและ # undefine'd ในตอนท้าย!
strager

ไม่ชอบสิ่งนั้นมาก - สิ่งแรกที่อยู่ในใจของฉันคือ DoSomeStackCheckStuff ทำให้หน่วยความจำเพิ่มขึ้นเนื่องจากข้อผิดพลาดบางอย่างและใครก็ตามที่อ่านรหัสจะไม่ทราบถึงการกำหนดค่าตอบแทนใหม่และสงสัยว่าเกิดอะไรขึ้น / hell /
gilligan

8
@strager แต่นั่นจะทำให้มันไร้ประโยชน์โดยพื้นฐาน ประเด็นทั้งหมดคือการเพิ่มการติดตามให้กับทุกการเรียกใช้ฟังก์ชัน มิฉะนั้นคุณจะเพิ่มการเรียกไปDoSomeStackCheckStuffยังฟังก์ชันที่คุณต้องการติดตาม
เหตุผล

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

ได้ผลจริงหรือ? :) ฉันจะเขียน#define return if((DoSomeStackCheckStuff) && 0) ; else return... ฉันเดาว่าบ้า!
Paolo Bonzini

22

ฉันชอบ "struct hack" ที่มีวัตถุขนาดไดนามิก ไซต์นี้อธิบายได้ดีเช่นกัน (แม้ว่าจะอ้างถึงเวอร์ชัน C99 ที่คุณสามารถเขียน "str []" เป็นสมาชิกตัวสุดท้ายของโครงสร้างได้) คุณสามารถสร้างสตริง "object" ดังนี้:

struct X {
    int len;
    char str[1];
};

int n = strlen("hello world");
struct X *string = malloc(sizeof(struct X) + n);
strcpy(string->str, "hello world");
string->len = n;

ที่นี่เราได้จัดสรรโครงสร้างของประเภท X บนฮีปซึ่งมีขนาดเท่ากับ int (สำหรับ len) บวกความยาวของ "hello world" บวก 1 (เนื่องจาก str 1รวมอยู่ในขนาดของ (X)

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


โดยส่วนตัวแล้วฉันพบว่ามันง่ายกว่าแค่ malloc () และ realloc () ด้วยตัวเองและใช้ strlen () เมื่อใดก็ตามที่ฉันต้องการหาความยาว แต่ถ้าคุณต้องการโปรแกรมที่ไม่เคยรู้ความยาวของสตริงและอาจจะต้องหามันหลาย ๆ ครั้งนี้น่าจะเป็นถนนที่ดีกว่า
Chris Lutz

4
"... เวอร์ชัน C99 ที่คุณสามารถเขียน" str [] "" ฉันเคยเห็นอาร์เรย์ขนาดศูนย์ในบริบทเช่นนี้เช่น str [0]; ค่อนข้างบ่อย ผมคิดว่ามันคือ C99 ฉันรู้ว่าคอมไพเลอร์รุ่นเก่าบ่นเกี่ยวกับอาร์เรย์ขนาดศูนย์
smcameron

3
ฉันชอบอันนี้เช่นกันอย่างไรก็ตามคุณควรใช้บางอย่างเช่นmalloc (offsetof (X, str) + numbytes)มิฉะนั้นสิ่งต่างๆจะผิดพลาดเนื่องจากปัญหาช่องว่างภายในและการจัดตำแหน่ง เช่น sizeof (struct X) อาจเป็น 8 ไม่ใช่ 5.
Fozi

3
@ โฟซี: จริงๆแล้วฉันไม่คิดว่าจะเป็นปัญหา เนื่องจากเวอร์ชันนี้มีstr[1](ไม่str[]) str 1 ไบต์จึงรวมอยู่ในไฟล์sizeof(struct X). นี้รวมถึงช่องว่างใด ๆ ระหว่างและlen str
Evan Teran

2
@Rusky: สิ่งนั้นจะส่งผลเสียอย่างไร? สมมติว่ามี "ช่องว่าง" strหลังจากที่ ตกลงเมื่อฉันจัดสรรsizeof(struct X) + 10สิ่งนี้ทำให้strมีประสิทธิภาพ10 - sizeof(int)(หรือมากกว่านั้นเนื่องจากเราบอกว่ามีช่องว่างภายใน) ใหญ่ การซ้อนทับ นี้strและช่องว่างภายในใด ๆ หลังจากนั้น วิธีเดียวที่จะมีความแตกต่างคือถ้ามีสมาชิกหลังจากนั้นก็strทำลายทุกสิ่งอยู่ดีสมาชิกที่ยืดหยุ่นจะต้องเป็นคนสุดท้าย การเว้นช่องว่างในตอนท้ายอาจทำให้จัดสรรได้มากเกินไป โปรดระบุตัวอย่างที่เจาะจงว่าอาจผิดพลาดได้อย่างไร
Evan Teran

17

โค้ดเชิงวัตถุด้วย C โดยเลียนแบบคลาส

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


2
ยังมีบางอย่างที่แปล C ++ เป็น C เหมือนที่ cfront เคยใช้หรือไม่?
MarkJ

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

จำเป็นต้องพูด +1 สำหรับสิ่งนั้น
Amit S

3
@ exDM69 การวางแนววัตถุเป็นวิธีคิดเกี่ยวกับปัญหามากพอ ๆ กับกระบวนทัศน์ในการเข้ารหัส คุณสามารถทำได้สำเร็จโดยไม่ต้องสืบทอด ฉันทำสิ่งนี้ในสองสามโปรเจ็กต์ก่อนที่จะกระโดดเข้าสู่ C ++
Mark Ransom


14

ใช้เคล็ดลับมาโครโง่ ๆ เพื่อทำให้คำจำกัดความของบันทึกง่ายขึ้น

#define COLUMNS(S,E) [(E) - (S) + 1]

typedef struct
{
    char studentNumber COLUMNS( 1,  9);
    char firstName     COLUMNS(10, 30);
    char lastName      COLUMNS(31, 51);

} StudentRecord;

11

สำหรับการสร้างตัวแปรที่อ่านอย่างเดียวในทุกโมดูลยกเว้นตัวแปรที่ประกาศไว้ใน:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere

นี่รู้สึกอันตราย นี่คือคำประกาศและคำจำกัดความที่ไม่ตรงกัน ขณะที่รวบรวมSource2.cคอมไพเลอร์อาจจะคิดว่าไม่เปลี่ยนแปลงแม้ในการเรียกฟังก์ชั่นMyVar Source1.c(โปรดทราบว่าในฐานะตัวแปร const จริงจะแตกต่างจากตัวชี้ถึง const ในกรณีหลังนี้วัตถุที่ชี้ไปยังอาจได้รับการแก้ไขผ่านตัวชี้อื่น)
jilles

1
สิ่งนี้ไม่ได้สร้างตัวแปรที่อ่านอย่างเดียวในหน่วยคอมไพล์บางหน่วยเท่านั้น สิ่งนี้ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนดไว้ (ดูหน้า 6.2.7.2 ของ ISO 9899 และหน้า 6.7.3.5)
Ales Hakl

8

การกะบิตถูกกำหนดให้เป็นจำนวนกะ 31 เท่านั้น (ในจำนวนเต็ม 32 บิต) ..

คุณจะทำอย่างไรถ้าคุณต้องการมีกะคำนวณที่ต้องทำงานกับค่ากะที่สูงขึ้นด้วย นี่คือวิธีที่ Theora vide-codec ทำ:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

หรืออ่านได้มากขึ้น:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

  return (a >> halfshift) >> otherhalf; 
}

การปฏิบัติงานตามที่แสดงไว้ด้านบนเป็นข้อตกลงที่ดีเร็วกว่าการใช้สาขาเช่นนี้:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}

... และ gcc อินไลน์จริงๆ :) +1
Tim Post

2
บนเครื่องของฉัน gcc-4.3.2 กำจัดสาขาในอันที่สองโดยใช้คำสั่ง cmov (การย้ายตามเงื่อนไข)
Adam Rosenfield

3
"ข้อตกลงที่ดีได้เร็วกว่าการใช้สาขา": ความแตกต่างที่ว่าสาขานั้นถูกต้องสำหรับค่าทั้งหมดของค่าทั้งหมดvในขณะที่halfshiftเคล็ดลับจะเพิ่มช่วงที่อนุญาตเป็นสองเท่าเป็น 63 ในสถาปัตยกรรม 32 บิตและ 127 สำหรับ 64 บิต
Pascal Cuoq

8

การประกาศตัวชี้อาร์เรย์เป็นฟังก์ชันสำหรับการใช้งานเครื่องสถานะ จำกัด

int (* fsm[])(void) = { ... }

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

ในระบบฝังตัวฉันมักจะแมป ISR เพื่อชี้ไปที่ตารางดังกล่าวและแสดงตามความจำเป็น (นอก ISR)


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

7

"เคล็ดลับ" ก่อนตัวประมวลผลที่ดีอีกอย่างหนึ่งคือการใช้อักขระ "#" เพื่อพิมพ์นิพจน์การดีบัก ตัวอย่างเช่น:

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

แก้ไข:โค้ดด้านล่างใช้ได้กับ C ++ เท่านั้น ขอบคุณ smcameron และ Evan Teran

ใช่เวลาคอมไพล์ยืนยันได้ดีเสมอไป นอกจากนี้ยังสามารถเขียนเป็น:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]

ไม่สามารถใช้มาโคร COMPILE_ASSERT สองครั้งได้เนื่องจากมันก่อให้เกิดเนมสเปซด้วย typedef และการใช้งานครั้งที่ 2 ได้รับ: ข้อผิดพลาด: การกำหนดค่าใหม่ของ typedef '__compile_time_assert'
smcameron

คุณลองสิ่งนี้จริงหรือไม่? คุณสามารถ "typedef foo;" กี่ครั้งก็ได้ตามที่คุณต้องการ นั่นคือวิธีที่คุณทำการประกาศล่วงหน้า ฉันใช้มันมา 2.5 ปีแล้วกับคอมไพเลอร์หลายตัวทั้ง gcc, VC และคอมไพเลอร์สำหรับสภาพแวดล้อมแบบฝังและไม่เคยพบปัญหาใด ๆ
Gilad Naor

ฉันเกลียดพรีโปรเซสเซอร์ C ... :(
hasen

1
ใช่ฉันลองแล้ว ฉันตัดและวางข้อความแสดงข้อผิดพลาดจากคอมไพเลอร์ซึ่งเป็น gcc
smcameron

1
@Gilad: มันถูกกฎหมายใน c ++ ที่จะมีตัวพิมพ์ซ้ำซ้อน แต่ไม่ใช่ใน c
Evan Teran

6

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


ฉันได้ใช้เทคนิคนี้ในทางปฏิบัติเพื่อสร้างโค้ดที่ขับเคลื่อนลำดับของ I / O แบบอะซิงโครนัสขึ้นอยู่กับที่มนุษย์อ่านไม่ได้ ข้อแตกต่างที่สำคัญคือฉันไม่ได้เก็บสถานะโครูทีนไว้ในstaticตัวแปร แต่จะจัดสรรโครงสร้างแบบไดนามิกและส่งตัวชี้ไปยังฟังก์ชันโครูทีนแทน มาโครจำนวนมากทำให้สิ่งนี้ถูกปากมากขึ้น มันไม่ดี แต่ดีกว่าเวอร์ชัน async / callback ที่กระโดดไปทั่วทุกที่ ฉันจะใช้เธรดสีเขียว (ผ่านทางswapcontext()* nixes) ถ้าทำได้
pmdj

6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

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


9
คุณใช้ # คำเตือนแทนไม่ได้หรือ
Stefano Borini

เห็นได้ชัดว่าฉันทำได้ มันไม่ได้มาตรฐานอย่างสมบูรณ์ แต่มันใช้งานได้ในคอมไพเลอร์ที่ฉันใช้ สิ่งที่น่าสนใจคือคอมไพลเลอร์ที่ฝังไว้แปล #define ในขณะที่ gcc ไม่ได้
gbarry

6

ฉันเป็นแฟนของ xor hacks:

สลับ 2 พอยน์เตอร์โดยไม่มีตัวชี้อุณหภูมิตัวที่สาม:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

หรือฉันชอบรายการที่เชื่อมโยง xor ที่มีตัวชี้เพียงตัวเดียว (http://en.wikipedia.org/wiki/XOR_linked_list)

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

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

เป็นต้น

หรือเพื่อย้อนกลับ:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

เป็นต้น

แม้ว่าจะไม่มีประโยชน์มากนัก (คุณไม่สามารถเริ่มการเดินทางจากโหนดตามอำเภอใจได้) ฉันคิดว่ามันเจ๋งมาก


5

อันนี้มาจากหนังสือ 'เชือกเพียงพอที่จะยิงตัวเองที่เท้า':

ในส่วนหัวประกาศ

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

ในคำสั่งการทดสอบรหัสของคุณเช่น:

D(printf("Test statement\n"));

do / while ช่วยในกรณีที่เนื้อหาของแมโครขยายเป็นหลายคำสั่ง

คำสั่งจะถูกพิมพ์หากไม่ได้ใช้แฟล็ก '-D RELEASE' สำหรับคอมไพเลอร์

จากนั้นคุณสามารถเช่น ส่งแฟล็กไปยัง makefile ของคุณเป็นต้น

ไม่แน่ใจว่ามันทำงานอย่างไรใน windows แต่ใน * nix ทำงานได้ดี


คุณอาจต้องการขยาย D (x) เป็น {} เมื่อมีการกำหนด RELEASE เพื่อให้เล่นได้ดีกับคำสั่ง if มิฉะนั้น "if (a) D (x);" จะขยายเป็นเพียง "if (a)" เมื่อคุณได้กำหนด RELEASE นั่นจะทำให้คุณมีข้อบกพร่องที่ดีใน
เวอร์ชันเผยแพร่

3
@MarkJ: ไม่. อย่างที่เป็นอยู่ "if (a) D (x);" ขยายเป็น "if (a);" ซึ่งดีมาก ถ้าคุณมี D (x) ขยายเป็น {} แล้ว "if (a) if (b) D (x); else foo ();" อย่างไม่ถูกต้องจะขยายเป็น "if (a) if (b) {}; else foo ();" ทำให้ "else foo ()" จับคู่กับคำที่สองถ้าแทนที่จะเป็น if แรก
Adam Rosenfield

พูดตามตรงฉันใช้มาโครนี้เป็นส่วนใหญ่ในการทดสอบคำสั่งการพิมพ์หรือถ้าฉันมีคำสั่งเงื่อนไขฉันจะใส่มันทั้งหมดเช่น D (ถ้า (a) foo (););
Simon Walker

1
@AdamRosenfield: ใช้#define D(x) do { } while(0)แทนจัดการกรณีนั้น (และสามารถนำไปใช้กับสาขาที่แทรกxเช่นกันเพื่อความสอดคล้อง)
rpetrich

3

Rusty ได้สร้างชุดเงื่อนไขการสร้างทั้งหมดในccanโปรดดูโมดูลการยืนยันการสร้าง:

#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

มีมาโครที่เป็นประโยชน์อื่น ๆ อีกมากมายในส่วนหัวที่แท้จริงซึ่งง่ายต่อการวางลงในตำแหน่ง

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


ใช่ฉันเพิ่งเจอ ccan และกำลังพิจารณาที่จะให้โค้ดบางอย่าง แต่ยังไม่ได้คิด "ccan way" ขอบคุณสำหรับลิงค์แม้ว่าแรงจูงใจในการดู ccan มากขึ้นซึ่งฉันหวังว่าจะได้รับแรงฉุด
smcameron

ฉันจะไม่กังวลเกี่ยวกับ 'ccan way' มากเกินไปจนกว่าจะมีการจัดตั้งมากขึ้น ... ตอนนี้ ccan-lint กำลังถูกเสนอเป็นโครงการ GSOC เป็นกลุ่มเล็ก ๆ และค่อนข้างเป็นมิตร .. และเป็นสถานที่ที่ดีในการทิ้งตัวอย่าง :)
Tim Post

BTW ฉันสังเกตว่า BuILD_ASSERT ของ Rusty นั้นเหมือนกับมาโครจากเคอร์เนลลินุกซ์ (ไม่น่าแปลกใจ) แต่ขาดหนึ่งใน "nots" (หรือ bangs หรือ! 's) และสังเกตว่าฉันคิดว่าตัวอย่างการใช้มาโครที่ฉันโพสต์คือ ไม่ถูกต้อง. ควรจะเป็น: "BUILD_BUG_ON ((sizeof (struct mystruct)% 8))"
smcameron

3

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


1
AFAIK ใน C89 / 90 ไม่มีการตรวจสอบการพิมพ์สำหรับ enums enums เป็นเพียง #defines ที่สะดวกกว่า
cschol

ด้านล่างของหน้า 39, 2nd ED K&R มีโอกาสอย่างน้อยสำหรับการตรวจสอบ
Jonathan Watmough

3

ไม่ได้เจาะจงเฉพาะ C แต่ฉันชอบตัวดำเนินการ XOR มาโดยตลอด สิ่งที่ยอดเยี่ยมอย่างหนึ่งที่ทำได้คือ "swap โดยไม่มีค่า temp":

int a = 1;
int b = 2;

printf("a = %d, b = %d\n", a, b);

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

printf("a = %d, b = %d\n", a, b);

ผลลัพธ์:

a = 1, b = 2

a = 2, b = 1


ก = 1; b = 2; ก = a + b; b = ab; a = ab; ก็ให้ผลลัพธ์เหมือนกัน
Grambot

สิ่งนี้จะสลับ a และ b: a ^ = b ^ = a ^ = b;
vikhyat

@TheCapn: การเพิ่มอาจล้นแม้ว่า
Michael Foukarakis


2

ฉันชอบแนวคิดที่container_ofใช้เป็นตัวอย่างในรายการ โดยทั่วไปคุณไม่จำเป็นต้องระบุnextและlastฟิลด์สำหรับแต่ละโครงสร้างซึ่งจะอยู่ในรายการ แต่คุณต้องต่อท้ายส่วนหัวโครงสร้างรายการกับรายการที่เชื่อมโยงจริง

ดูinclude/linux/list.hตัวอย่างชีวิตจริง


1

ฉันคิดว่าการใช้ตัวชี้userdataนั้นค่อนข้างเรียบร้อย วันนี้แฟชั่นที่สูญเสียพื้น คุณสมบัติ C ไม่มากนัก แต่ใช้งานง่ายใน C


1
ฉันหวังว่าฉันจะเข้าใจว่าคุณหมายถึงอะไรที่นี่ คุณช่วยอธิบายเพิ่มเติมได้ไหม ตัวชี้ข้อมูลผู้ใช้คืออะไร?
Zan Lynx

1
โปรดดูที่นี่stackoverflow.com/questions/602826/…
epatel

เป็นหลักสำหรับการโทรกลับ เป็นข้อมูลบางส่วนที่คุณต้องการให้กลับไปทุกครั้งที่การโทรกลับเริ่มทำงาน มีประโยชน์อย่างยิ่งสำหรับการส่งผ่าน C ++ ตัวชี้นี้ไปยังการเรียกกลับเพื่อให้คุณสามารถผูกวัตถุเข้ากับเหตุการณ์ได้
Evan Teran

อ่าใช่ ขอบคุณ. ฉันใช้สิ่งนี้มาก แต่ฉันไม่เคยเรียกมันว่า
Zan Lynx

1

ฉันใช้X-Macrosเพื่อให้พรีคอมไพเลอร์สร้างโค้ด ซึ่งมีประโยชน์อย่างยิ่งสำหรับการกำหนดค่าข้อผิดพลาดและสตริงข้อผิดพลาดที่เกี่ยวข้องในที่เดียว แต่สามารถไปได้ไกลกว่านั้น


1

codebase ของเรามีเคล็ดลับคล้ายกับ

#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

ซึ่งช่วยในการติดตามการรั่วไหลของหน่วยความจำในโหมดดีบัก ฉันคิดเสมอว่าสิ่งนี้เจ๋ง


1

สนุกกับมาโคร:

#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)

/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}

0

นี่คือตัวอย่างวิธีทำให้รหัส C ไม่ทราบโดยสิ้นเชิงเกี่ยวกับสิ่งที่ HW ใช้ในการเรียกใช้แอป main.c ทำการตั้งค่าจากนั้นสามารถใช้เลเยอร์ฟรีบนคอมไพเลอร์ / arch ใด ๆ ฉันคิดว่ามันค่อนข้างเรียบร้อยสำหรับการแยกโค้ด C เล็กน้อยดังนั้นจึงไม่ได้เป็นเรื่องที่น่ากลัว

เพิ่มตัวอย่างที่รวบรวมได้ที่นี่

/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x\n",my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init\n");
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x\n",send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}

4
ระวังให้ละเอียดอาจอธิบายการใช้งานจริง?
Leonardo Herrera

เป็นตัวอย่าง wxample ถ้าฉันต้องเขียนโปรแกรมทดสอบโดยใช้อินเตอร์เฟส som HW ที่สร้าง interupts ในตอนท้าย จากนั้นโมดูลนี้สามารถตั้งค่าเพื่อเรียกใช้ฟังก์ชันนอกขอบเขตปกติเป็นตัวจัดการสัญญาณ / ขัดจังหวะ
eaanon01

0
if(---------)  
printf("hello");  
else   
printf("hi");

กรอกข้อมูลในช่องว่างเพื่อไม่ให้สวัสดีหรือสวัสดีปรากฏในผลลัพธ์
ans:fclose(stdout)


คุณสามารถจัดรูปแบบโค้ดด้วย{}ปุ่มแถบเครื่องมือ (ฉันทำเพื่อคุณแล้ว) ปุ่ม "ใบเสนอราคา" ไม่เว้นช่องว่างหรือใช้การเน้นไวยากรณ์
ÁlvaroGonzález
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.