Thủ thuật lập trình C yêu thích của bạn là gì? [đóng cửa]


134

Ví dụ, gần đây tôi đã bắt gặp điều này trong kernel linux:

/ * Buộc lỗi biên dịch nếu điều kiện là đúng * /
#define BUILD_BUG_ON (điều kiện) ((void) sizeof (char [1 - 2 * !! (điều kiện)]))

Vì vậy, trong mã của bạn, nếu bạn có một số cấu trúc phải có, giả sử có nhiều kích thước 8 byte, có thể do một số hạn chế về phần cứng, bạn có thể thực hiện:

BUILD_BUG_ON ((sizeof (struct myst struct)% 8)! = 0);

và nó sẽ không biên dịch trừ khi kích thước của struct struct struct là bội số của 8 và nếu nó là bội số của 8, thì không có mã thời gian chạy nào được tạo ra cả.

Một mẹo khác mà tôi biết là từ cuốn sách "Đồ họa đá quý" cho phép một tệp tiêu đề duy nhất để khai báo và khởi tạo các biến trong một mô-đun trong khi trong các mô-đun khác sử dụng mô-đun đó, chỉ đơn thuần khai báo chúng là externs.

#ifdef DEFINE_MYHEADER_GLOBALS
#define TOÀN CẦU
#define INIT (x, y) (x) = (y)
#else
#define bên ngoài TOÀN CẦU
#define INIT (x, y)
#endif

TOÀN CẦU int INIT (x, 0);
TOÀN CẦU int somefunc (int a, int b);

Cùng với đó, mã xác định x và somefunc thực hiện:

#define DEFINE_MYHEADER_GLOBALS
#inc loại "the_above_header_file.h"

trong khi mã chỉ đơn thuần sử dụng x và somefunc () thì:

#inc loại "the_above_header_file.h"

Vì vậy, bạn nhận được một tệp tiêu đề khai báo cả hai phiên bản toàn cầu và nguyên mẫu hàm khi cần và các khai báo bên ngoài tương ứng.

Vì vậy, các thủ thuật lập trình C yêu thích của bạn dọc theo những dòng đó là gì?


9
Điều này có vẻ giống như thủ thuật tiền xử lý C.
jmucchiello

Về BUILD_BUG_ONmacro, có gì sai khi sử dụng #errorbên trong và #if?
Ricardo

Câu trả lời:


80

C99 cung cấp một số thứ thực sự thú vị bằng cách sử dụng mảng ẩn danh:

Loại bỏ các biến vô nghĩa

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

trở thành

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

Vượt qua một lượng đối số

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

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

Danh sách liên kết tĩnh

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;
    }
}

Bất kỳ tôi chắc chắn nhiều kỹ thuật tuyệt vời khác tôi không nghĩ đến.


2
Tôi tin rằng ví dụ đầu tiên của bạn cũng có thể được viết dưới dạng &(int){1}, nếu bạn muốn làm rõ hơn một chút ý định của bạn ở đây là gì.
Lily Ballard

67

Trong khi đọc mã nguồn Quake 2, tôi đã nghĩ ra một thứ như thế này:

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

(ít nhiều, tôi không có mã để kiểm tra ngay bây giờ).

Kể từ đó, một thế giới mới của việc sử dụng sáng tạo bộ tiền xử lý đã mở ra trước mắt tôi. Tôi không còn chỉ bao gồm các tiêu đề, mà toàn bộ các đoạn mã bây giờ và sau đó (nó cải thiện khả năng sử dụng lại rất nhiều) :-p

Cảm ơn John Carmack! xD


13
Bạn không thể nói carmack trong một luồng tối ưu hóa mà không đề cập đến sqrt nghịch đảo nhanh có trong nguồn động đất. vi.wikipedia.org/wiki/Fast_inverse_sapes_root
pg1989

Anh ấy đã nhận được 0x5f3759df từ đâu ngay từ đầu?
RSH1

2
@RoryHarvey: Từ những gì tôi có thể tìm thấy khi tìm kiếm nó, có vẻ như nó hoàn toàn theo kinh nghiệm. Một số nghiên cứu (tôi không nhớ nơi tôi nhìn thấy chúng) đã chứng minh rằng nó gần với tối ưu, nhưng không hoàn toàn tối ưu. Tương tự như vậy, dường như trong 64 bit, giá trị đã được phát hiện, thay vì tính toán.
Matthieu M.

50

Tôi thích sử dụng = {0};để khởi tạo cấu trúc mà không cần gọi bộ nhớ.

struct something X = {0};

Điều này sẽ khởi tạo tất cả các thành viên của struct (hoặc mảng) về 0 (nhưng không phải bất kỳ byte đệm nào - sử dụng bộ nhớ nếu bạn cũng cần phải làm như vậy).

Nhưng bạn nên lưu ý rằng có một số vấn đề với điều này đối với các cấu trúc lớn, được phân bổ động .


Bằng cách này, không cần thiết cho các biến toàn cầu.
strager

5
Không cần thiết cho các biến tĩnh . Các biến toàn cục có thể bằng 0, nhưng đó không phải là một yêu cầu.
Jamie

4
Đôi khi tôi mở rộng điều này thành: const struct something zero_something = { 0 };và sau đó tôi có thể đặt lại một biến khi đang di chuyển bằng struct something X = zero_something;hoặc một phần thông qua một thói quen tôi có thể sử dụng 'X = zero_s Something;'. Sự phản đối duy nhất có thể là nó liên quan đến việc đọc dữ liệu từ đâu đó; những ngày này, một 'bộ nhớ ()' có thể nhanh hơn - nhưng tôi thích sự rõ ràng của bài tập và cũng có thể sử dụng các giá trị khác không trong bộ khởi tạo (và bộ nhớ () theo sau là các chỉnh sửa cho từng thành viên có thể chậm hơn một bản sao đơn giản).
Jonathan Leffler

45

Nếu chúng ta đang nói về các thủ thuật c, yêu thích của tôi phải là Thiết bị của Duff để không kiểm soát vòng lặp! Tôi chỉ đang chờ cơ hội thích hợp để cùng tôi thực sự sử dụng nó trong sự tức giận ...


4
Tôi đã sử dụng nó một lần để tạo ra mức tăng hiệu suất có thể đo được, nhưng ngày nay nó không hữu ích trên nhiều phần cứng. Luôn hồ sơ!
Dan Olson

6
Vâng, loại người không hiểu ngữ cảnh Thiết bị của Duff đã được tạo trong: "khả năng đọc mã" là vô ích nếu mã không đủ nhanh để hoạt động. Có lẽ không ai trong số những người đánh giá thấp bạn đã từng phải viết mã cho thời gian thực khó khăn.
Rob K

1
+1, tôi thực sự cần sử dụng thiết bị của Duff một vài lần. Lần đầu tiên là một vòng lặp về cơ bản chỉ là sao chép công cụ và thực hiện một số biến đổi nhỏ trên đường đi. Nó nhanh hơn nhiều so với một memcpy () đơn giản trong kiến ​​trúc đó.
Makis

3
Sự tức giận sẽ đến từ các đồng nghiệp và người kế nhiệm của bạn, những người phải duy trì mã của bạn sau bạn.
Jonathan Leffler

1
Như tôi đã nói, tôi vẫn đang chờ cơ hội phù hợp - nhưng chưa ai làm phiền tôi đủ. Tôi đã viết C được khoảng 25 năm rồi, tôi nghĩ rằng lần đầu tiên tôi bắt gặp thiết bị của Duff vào đầu những năm 90 và tôi chưa phải sử dụng nó. Như những người khác đã nhận xét loại thủ thuật này ngày càng ít hữu dụng hơn khi các trình biên dịch trở nên tốt hơn với loại tối ưu hóa này.
Jackson

42

sử dụng __FILE____LINE__để gỡ lỗi

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

6
Trên một số trình biên dịch, bạn cũng nhận được FUNCTION .
JBRWilkinson

11
__FUNCTION__chỉ là một bí danh cho __func__, và __func__là trong c99. Khá tiện dụng. __PRETTY_FUNCTION__trong C (GCC) chỉ là một bí danh khác __func__, nhưng trong C ++, nó sẽ giúp bạn có được chữ ký hàm đầy đủ.
sklnd

FILE hiển thị đường dẫn đầy đủ của tên tệp vì vậy tôi sử dụng tên cơ sở ( FILE )
Jeegar Patel

31

Trong C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};

28

Khi một người bạn đời của tôi và tôi xác định lại trở về để tìm một lỗi tham nhũng ngăn xếp khó khăn.

Cái gì đó như:

#define return DoSomeStackCheckStuff, return

4
Hy vọng rằng đó là # định nghĩa trong phần thân hàm và # không xác định cuối cùng!
strager

Không thích điều đó - điều đầu tiên tôi nghĩ đến là DoSomeStackCheckStuff làm hỏng bộ nhớ vì một số lỗi và bất cứ ai đang đọc mã đều không biết về việc xác định lại sự trở lại và tự hỏi chuyện gì đang xảy ra.
gilligan

8
@strager Nhưng điều đó về cơ bản sẽ vô dụng. Toàn bộ vấn đề là thêm một số dấu vết cho mỗi lệnh gọi hàm. Nếu không, bạn chỉ cần thêm một cuộc gọi DoSomeStackCheckStuffđến các chức năng bạn muốn theo dõi.
Cluless

1
@gilligan Tôi không nghĩ rằng đây là loại thứ bạn để lại được bật mọi lúc; nó có vẻ khá tiện dụng cho công việc sửa lỗi one-shot.
sunetos

nó có thực sự hiệu quả không? :) Tôi sẽ viết #define return if((DoSomeStackCheckStuff) && 0) ; else return... đúng như tôi đoán!
Paolo Bonzini

22

Tôi thích "struct hack" vì có một đối tượng có kích thước động. Trang web này cũng giải thích nó khá tốt (mặc dù họ đề cập đến phiên bản C99 nơi bạn có thể viết "str []" là thành viên cuối cùng của một cấu trúc). bạn có thể tạo một chuỗi "đối tượng" như thế này:

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;

Ở đây, chúng tôi đã phân bổ cấu trúc của loại X trên heap có kích thước của một int (cho len), cộng với độ dài của "hello world", cộng với 1 (kể từ str 1 được bao gồm trong sizeof (X).

Nó thường hữu ích khi bạn muốn có một "tiêu đề" ngay trước một số dữ liệu có độ dài thay đổi trong cùng một khối.


Cá nhân tôi thấy bản thân mình chỉ cần malloc () và realloc () và sử dụng strlen () bất cứ khi nào tôi cần tìm độ dài, nhưng nếu bạn cần một chương trình không bao giờ biết độ dài của chuỗi và có thể sẽ cần tìm nhiều lần này, đây có lẽ là con đường tốt hơn
Chris Lutz

4
"... phiên bản C99 nơi bạn có thể viết" str [] "" Tôi đã thấy các mảng có kích thước bằng 0 trong bối cảnh như vậy, như str [0]; khá thường xuyên. Tôi nghĩ đó là C99. Tôi biết trình biên dịch cũ hơn phàn nàn về mảng không có kích thước mặc dù.
smcameron

3
Tôi cũng thích cái này, tuy nhiên, bạn nên sử dụng cái gì đó như malloc (offsetof (X, str) + numbyte) nếu không mọi thứ sẽ sai vì vấn đề đệm và căn chỉnh. Ví dụ sizeof (struct X) có thể là 8, không phải 5.
Fozi

3
@Fozi: Tôi thực sự không nghĩ rằng đó sẽ là một vấn đề. Vì phiên bản này có str[1](không str[]) 1 byte str được bao gồm trong sizeof(struct X). Điều này bao gồm bất kỳ phần đệm giữa lenstr.
Evan Teran

2
@Rusky: Làm thế nào điều đó sẽ ảnh hưởng tiêu cực? Giả sử có "đệm" sau str. OK, khi tôi phân bổ sizeof(struct X) + 10Sau đó, điều này làm cho strhiệu quả 10 - sizeof(int)(hoặc nhiều hơn, vì chúng tôi đã nói có phần đệm) lớn. Điều này lớp phủ str và bất kỳ phần đệm sau nó. Cách duy nhất mà nó sẽ có bất kỳ sự khác biệt, là nếu có một thành viên sau strđó phá vỡ toàn bộ mọi thứ, các thành viên linh hoạt phải là người cuối cùng. Bất kỳ phần đệm nào ở cuối sẽ chỉ có thể gây ra quá nhiều để được phân bổ. Vui lòng cung cấp một ví dụ cụ thể về cách nó thực sự có thể đi sai.
Evan Teran

17

Mã hướng đối tượng với C, bằng cách mô phỏng các lớp.

Đơn giản chỉ cần tạo một cấu trúc và một tập hợp các hàm lấy một con trỏ tới cấu trúc đó làm tham số đầu tiên.


2
Vẫn còn một cái gì đó ngoài đó dịch C ++ sang C, giống như cfront đã từng?
MarkJ

11
Đây không phải là định hướng đối tượng. Đối với OO có tính kế thừa, bạn sẽ cần thêm một số loại bảng chức năng ảo vào cấu trúc đối tượng của mình, có thể bị quá tải bởi "các lớp con". Có rất nhiều khung kiểu chữ "C với các lớp" được nướng ngoài kia cho mục đích này nhưng tôi khuyên bạn nên tránh xa nó.
exDM69

Nó cần phải nói. +1 cho điều đó.
Amit S

3
@ exDM69, định hướng đối tượng là cách suy nghĩ về một vấn đề cũng giống như mô hình mã hóa; bạn có thể làm điều đó thành công mà không cần kế thừa. Tôi đã làm điều này trên một vài dự án trước khi nhảy đầy đủ vào C ++.
Đánh dấu tiền chuộc

16

Thay vì

printf("counter=%d\n",counter);

Sử dụng

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);

14

Sử dụng một thủ thuật vĩ mô ngu ngốc để làm cho các định nghĩa hồ sơ dễ dàng hơn để duy trì.

#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

Để tạo một biến chỉ đọc trong tất cả các mô-đun ngoại trừ biến được khai báo trong:

// 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

Điều này cảm thấy nguy hiểm. Đây là những tuyên bố và định nghĩa không phù hợp. Trong khi biên dịch Source2.c, trình biên dịch có thể cho rằng điều MyVarđó không thay đổi, thậm chí qua một lệnh gọi hàm Source1.c. (Lưu ý rằng đây là biến const thực tế, khác với con trỏ so với const. Trong trường hợp sau, đối tượng trỏ tới vẫn có thể được sửa đổi thông qua một con trỏ khác.)
jilles

1
Điều này không tạo ra biến mà chỉ đọc trong một số đơn vị biên dịch. Điều này tạo ra hành vi không xác định (xem trang 6.2.7.2 của ISO 9899 và cả trang 6.7.3.5).
Ales Hakl

8

Dịch chuyển bit chỉ được xác định tối đa là lượng dịch chuyển là 31 (trên số nguyên 32 bit) ..

Bạn sẽ làm gì nếu bạn muốn có một ca làm việc được tính toán cần phải làm việc với các giá trị ca cao hơn? Đây là cách The vide-codec thực hiện nó:

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

Hoặc dễ đọc hơn nhiều:

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

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

Thực hiện nhiệm vụ theo cách hiển thị ở trên nhanh hơn nhiều so với sử dụng một nhánh như thế này:

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

... và gcc thực sự nội tuyến nó :) +1
Tim Post

2
Trên máy của tôi, gcc-4.3.2 loại bỏ nhánh trong cái thứ hai bằng cách sử dụng lệnh cmov (di chuyển có điều kiện)
Adam Rosenfield

3
"Nhanh hơn nhiều so với sử dụng một nhánh": sự khác biệt là nhánh đó đúng với tất cả các giá trị của v, trong khi halfshiftmẹo chỉ tăng gấp đôi phạm vi cho phép lên 63 trên kiến ​​trúc 32 bit và 127 trên cấu trúc 64 bit.
Pascal Cuoq

8

Khai báo mảng con trỏ tới các hàm để thực hiện các máy trạng thái hữu hạn.

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

Ưu điểm dễ chịu nhất là đơn giản để buộc mỗi kích thích / trạng thái kiểm tra tất cả các đường dẫn mã.

Trong một hệ thống nhúng, tôi thường ánh xạ ISR để trỏ đến một bảng như vậy và chỉnh sửa nó khi cần thiết (bên ngoài ISR).


Một kỹ thuật tôi thích với điều này là, nếu bạn có một hàm yêu cầu khởi tạo, bạn khởi tạo con trỏ bằng một lệnh gọi đến thói quen khởi tạo. Khi nó chạy, điều cuối cùng nó làm là thay thế con trỏ bằng một con trỏ đến hàm thực tế, sau đó gọi hàm đó. Theo cách đó, trình khởi tạo tự động được gọi lần đầu tiên hàm được gọi và hàm thực được gọi mỗi lần tiếp theo.
TMN

7

Một "mẹo" tiền xử lý hay khác là sử dụng ký tự "#" để in các biểu thức gỡ lỗi. Ví dụ:

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

chỉnh sửa: mã dưới đây chỉ hoạt động trên C ++. Cảm ơn smcameron và Evan Teran.

Vâng, thời gian biên dịch khẳng định luôn luôn tuyệt vời. Nó cũng có thể được viết là:

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

Mặc dù vậy, macro COMPILE_ASSERT không thể được sử dụng hai lần, vì nó làm ô nhiễm không gian tên bằng một typedef và cách sử dụng thứ 2 là: error: xác định lại typedef '__compile_time_assert'
smcameron

Bạn đã thực sự thử điều này? Bạn có thể "typedef foo;" bao nhiêu lần tùy thích Đó là cách bạn làm dự đoán. Tôi đã sử dụng nó 2,5 năm nay trên một số trình biên dịch, cả gcc, VC và trình biên dịch cho môi trường nhúng và không bao giờ gặp phải bất kỳ khó khăn nào.
Gilad Naor

Tôi ghét bộ tiền xử lý C ... :(
hasen

1
Vâng, tôi đã thử nó. Tôi cắt và dán thông báo lỗi từ trình biên dịch, đó là gcc.
smcameron

1
@Gilad: hợp pháp trong c ++ là có typedefs dư thừa, nhưng không có trong c.
Evan Teran

6

Tôi thực sự sẽ không gọi nó là một trò lừa ưa thích, vì tôi chưa bao giờ sử dụng nó, nhưng việc nhắc đến Thiết bị của Duff đã nhắc nhở tôi về bài viết này về việc triển khai Coroutines trong C. Nó luôn mang đến cho tôi một tiếng cười, nhưng tôi chắc chắn rằng nó có thể có ích một thời gian


Tôi thực sự đã sử dụng kỹ thuật này trong thực tế để làm cho mã điều khiển một chuỗi các I / O không đồng bộ phụ thuộc một cách mơ hồ có thể đọc được. Sự khác biệt chính là tôi không lưu trữ trạng thái coroutine trong một staticbiến mà thay vào đó phân bổ một cấu trúc động và chuyển một con trỏ tới đó vào hàm coroutine. Một loạt các macro làm cho điều này trở nên ngon miệng hơn. Nó không đẹp nhưng tốt hơn phiên bản async / gọi lại nhảy khắp nơi. Tôi sẽ sử dụng các chủ đề màu xanh lá cây (thông qua swapcontext()trên * nixes) nếu tôi có thể.
pmdj

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

Trong khi (0); không có tác dụng đối với chương trình, nhưng trình biên dịch sẽ đưa ra cảnh báo về "điều này không có gì", điều đó đủ để khiến tôi phải nhìn vào dòng vi phạm và sau đó xem lý do thực sự tôi muốn gọi sự chú ý đến nó.


9
bạn không thể sử dụng #warning thay thế?
Stefano Borini

Rõ ràng, tôi có thể. Nó không hoàn toàn tiêu chuẩn, nhưng nó hoạt động trong các trình biên dịch mà tôi sử dụng. Thật thú vị, trình biên dịch nhúng đã dịch một #define, trong khi gcc thì không.
gbarry

6

Tôi là một fan hâm mộ của hack xor:

Hoán đổi 2 con trỏ không có con trỏ tạm thời thứ ba:

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

Hoặc tôi thực sự thích danh sách liên kết xor chỉ với một con trỏ. (http://en.wikipedia.org/wiki/XOR_linked_list)

Mỗi nút trong danh sách được liên kết là Xor của nút trước và nút tiếp theo. Để di chuyển về phía trước, địa chỉ của các nút được tìm thấy theo cách sau:

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

Vân vân.

hoặc để di chuyển ngược:

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;

Vân vân.

Mặc dù không hữu ích lắm (bạn không thể bắt đầu di chuyển từ một nút tùy ý) tôi thấy nó rất tuyệt.


5

Cuốn này xuất phát từ cuốn sách 'Đủ dây để tự bắn vào chân mình':

Trong tiêu đề khai báo

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

Trong mã của bạn, nơi kiểm tra báo cáo ví dụ:

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

Do / while giúp trong trường hợp nội dung của macro mở rộng thành nhiều câu lệnh.

Câu lệnh sẽ chỉ được in nếu cờ '-D RELEASE' cho trình biên dịch không được sử dụng.

Bạn có thể sau đó ví dụ. chuyển cờ đến tệp thực hiện của bạn, v.v.

Không chắc cách thức hoạt động trong windows nhưng trong * nix nó hoạt động tốt


Bạn có thể muốn mở rộng D (x) thành {} khi xác định RELEASE, để nó chơi tốt với các câu lệnh if. Mặt khác "nếu (a) D (x);" sẽ mở rộng thành chỉ "nếu (a)" khi bạn đã xác định ĐÁNG TIN CẬY. Điều đó sẽ cung cấp cho bạn một số lỗi hay trong bản án
ĐÁNG TIN CẬY

3
@MarkJ: KHÔNG. Cách nó là, "nếu (a) D (x);" mở rộng thành "if (a);" Điều đó là hoàn toàn tốt. Nếu bạn có D (x) mở rộng thành {}, thì "if (a) if (b) D (x); other foo ();" sẽ TĂNG CƯỜNG mở rộng thành "if (a) if (b) {}; other foo ();", khiến "other foo ()" khớp với thứ hai nếu thay vì if đầu tiên.
Adam Rosenfield

Thành thật mà nói, tôi chủ yếu sử dụng macro này để kiểm tra các câu lệnh in hoặc nếu tôi có một câu lệnh có điều kiện, tôi sẽ gửi kèm theo tất cả, ví dụ như. D (nếu (a) foo (););
Simon Walker

1
@AdamRosenfield: #define D(x) do { } while(0)Thay vào đó, sử dụng xử lý trường hợp đó (và có thể được áp dụng cho chi nhánh chèn xđể đảm bảo tính nhất quán)
rpetrich

3

Rusty thực sự đã tạo ra một tập hợp các điều kiện xây dựng trong ccan , hãy kiểm tra mô đun khẳng định xây dựng:

#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;
}

Có rất nhiều macro hữu ích khác trong tiêu đề thực tế, dễ dàng thả vào vị trí.

Tôi cố gắng, với tất cả khả năng của mình để chống lại sự lôi kéo của mặt tối (và lạm dụng tiền xử lý) bằng cách chủ yếu bám vào các hàm nội tuyến, nhưng tôi thích các macro thông minh, hữu ích như những gì bạn mô tả.


Vâng, gần đây tôi đã đi qua ccan, và đang xem xét đóng góp một số mã, nhưng vẫn chưa quấn đầu quanh "con đường ccan". Cảm ơn vì liên kết mặc dù, có thêm động lực để nhìn vào ccan, điều mà tôi thực sự hy vọng sẽ có được một lực kéo.
smcameron

Chà, tôi sẽ không quá quan tâm đến 'ccan way' cho đến khi nó được thiết lập nhiều hơn ... ngay bây giờ ccan-lint đang được đề xuất như một dự án GSOC. Đây là một nhóm nhỏ và khá thân thiện .. và là nơi tuyệt vời để bỏ các đoạn trích :)
Tim Post

BTW, tôi nhận thấy BuILD_ASSERT của Rusty giống như macro từ kernel linux (không có gì đáng ngạc nhiên) nhưng thiếu một trong những "nots" (hoặc bangs, hoặc!) Và nhận thấy rằng, tôi nghĩ cách sử dụng macro của tôi đã đăng là không chính xác. Đáng lẽ phải là: "BUILD_BUG_ON ((sizeof (struct myst struct)% 8))"
smcameron

3

Hai cuốn sách nguồn tốt cho loại công cụ này là Thực hành lập trìnhviết mã rắn . Một trong số họ (tôi không nhớ là gì) nói: Thích enum hơn #define nơi bạn có thể, bởi vì enum được trình biên dịch kiểm tra.


1
AFAIK, trong C89 / 90 KHÔNG có lỗi đánh máy cho enums. enums chỉ là bằng cách nào đó thuận tiện hơn #defines.
cschol

Cuối trang 39, 2nd ED K & R. Ít nhất là có cơ hội để kiểm tra.
Jonathan Watmough

3

Không cụ thể với C, nhưng tôi luôn thích toán tử XOR. Một điều thú vị mà nó có thể làm là "trao đổi mà không có giá trị tạm thời":

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);

Kết quả:

a = 1, b = 2

a = 2, b = 1


a = 1; b = 2; a = a + b; b = ab; a = ab; cũng cho kết quả tương tự
Grambot

Điều này cũng sẽ trao đổi a và b: a ^ = b ^ = a ^ = b;
vikhyat

@TheCapn: mặc dù có thể tràn.
Michael Foukarakis


2

Tôi thích khái niệm container_ofđược sử dụng ví dụ trong danh sách. Về cơ bản, bạn không cần chỉ định nextlastcác trường cho từng cấu trúc sẽ có trong danh sách. Thay vào đó, bạn nối tiêu đề cấu trúc danh sách vào các mục được liên kết thực tế.

Có một cái nhìn về include/linux/list.hcác ví dụ thực tế.


1

Tôi nghĩ rằng việc sử dụng con trỏ userdata là khá gọn gàng. Một thời trang mất đất ngày nay. Đây không phải là một tính năng C nhưng khá dễ sử dụng trong C.


1
Tôi ước rằng tôi hiểu ý của bạn ở đây. Bạn có thể giải thích thêm? Một con trỏ userdata là gì?
Zan Lynx

1
Xin vui lòng xem tại đây stackoverflow.com/questions/602826/ Cách
epatel

nó chủ yếu cho các cuộc gọi lại. Đó là một số dữ liệu bạn muốn được trả lại cho bạn mỗi khi cuộc gọi lại được thực hiện. Đặc biệt hữu ích khi truyền C ++ con trỏ này cho một cuộc gọi lại để bạn có thể buộc một đối tượng vào một sự kiện.
Evan Teran

À, vâng. Cảm ơn. Tôi sử dụng nó rất nhiều, nhưng tôi chưa bao giờ gọi nó như vậy.
Zan Lynx

1

Tôi sử dụng X-Macros để cho phép trình biên dịch trước tạo mã. Chúng đặc biệt hữu ích để xác định các giá trị lỗi và các chuỗi lỗi liên quan ở một nơi, nhưng chúng có thể vượt xa điều đó.


1

Cơ sở mã của chúng tôi có một mẹo tương tự như

#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
}

cho phép theo dõi rò rỉ bộ nhớ trong chế độ gỡ lỗi. Tôi luôn nghĩ rằng điều này là mát mẻ.


1

Vui với macro:

#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

Dưới đây là một ví dụ về cách làm cho mã C hoàn toàn không biết về những gì thực sự được sử dụng của CTNH để chạy ứng dụng. Main.c thực hiện thiết lập và sau đó lớp miễn phí có thể được thực hiện trên bất kỳ trình biên dịch / vòm nào. Tôi nghĩ rằng nó khá gọn gàng để trừu tượng hóa mã C một chút, vì vậy nó không phải là đặc biệt.

Thêm một ví dụ tổng hợp đầy đủ ở đây.

/* 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
Quan tâm đến công phu, có thể giải thích một sử dụng thực tế?
Leonardo Herrera

Là một wxample nếu tôi phải viết một chương trình thử nghiệm bằng giao diện som HW tạo ra các interup cuối cùng. Sau đó, mô-đun này có thể được thiết lập để thực hiện một chức năng bên ngoài phạm vi bình thường như một trình xử lý tín hiệu / ngắt.
eaanon01

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

Điền vào chỗ trống để không chào và cũng không xuất hiện trong đầu ra.
ans:fclose(stdout)


bạn có thể định dạng mã bằng {}nút thanh công cụ (Tôi đã thực hiện nó cho bạn). Nút "Trích dẫn" không giữ khoảng trắng hoặc áp dụng tô sáng cú pháp.
Álvaro González
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.