Các tính năng ẩn của C


141

Tôi biết có một tiêu chuẩn đằng sau tất cả các cài đặt trình biên dịch C, vì vậy không nên có các tính năng ẩn. Mặc dù vậy, tôi chắc chắn rằng tất cả các nhà phát triển C đều có những thủ thuật ẩn / bí mật mà họ sử dụng mọi lúc.


Sẽ thật tuyệt nếu bạn / ai đó chỉnh sửa câu hỏi của người dùng để chỉ ra sự lựa chọn các tính năng ẩn tốt nhất, chẳng hạn như trong các phiên bản C # và Perl của câu hỏi này.
Donal Fellows

Câu trả lời:


62

Chức năng con trỏ. Bạn có thể sử dụng một bảng các con trỏ hàm để thực hiện, ví dụ, các trình thông dịch mã luồng gián tiếp nhanh (FORTH) hoặc các bộ gửi mã byte hoặc để mô phỏng các phương thức ảo giống như OO.

Sau đó, có những viên ngọc ẩn trong thư viện tiêu chuẩn, chẳng hạn như qsort (), bsearch (), strpbrk (), strcspn () [hai cái sau hữu ích để thực hiện thay thế strtok ()].

Một sai lầm của C là tràn số học đã ký là hành vi không xác định (UB). Vì vậy, bất cứ khi nào bạn thấy một biểu thức như x + y, cả hai đều được ký int, nó có thể có khả năng tràn và gây ra UB.


29
Nhưng nếu họ đã chỉ định hành vi trên tràn, thì nó sẽ làm cho nó rất chậm trên các kiến ​​trúc nơi đó không phải là hành vi bình thường. Chi phí thời gian chạy rất thấp luôn là mục tiêu thiết kế của C và điều đó có nghĩa là rất nhiều thứ như thế này không được xác định.
Mark Baker

9
Tôi biết rất rõ tại sao tràn là UB. Đây vẫn là một hành vi sai trái, bởi vì tiêu chuẩn ít nhất phải có các thói quen thư viện được cung cấp có thể kiểm tra mức tràn số học (của tất cả các hoạt động cơ bản) sẽ gây ra UB.
zvrba

2
@zvrba, "các thói quen thư viện có thể kiểm tra mức tràn số học (của tất cả các hoạt động cơ bản)" nếu bạn đã thêm điều này thì bạn sẽ phải chịu một cú đánh hiệu suất đáng kể cho bất kỳ hoạt động số học số nguyên nào. ===== Nghiên cứu trường hợp Matlab cụ thể ADDS tính năng kiểm soát hành vi tràn số nguyên để gói hoặc bão hòa. Và nó cũng ném ra một ngoại lệ bất cứ khi nào xảy ra tràn ==> Hiệu suất của các hoạt động số nguyên Matlab: RẤT RẤT. Kết luận của riêng tôi: Tôi nghĩ Matlab là một trường hợp nghiên cứu hấp dẫn cho thấy lý do tại sao bạn không muốn kiểm tra tràn số nguyên.
Trevor Boyd Smith

15
Tôi đã nói rằng tiêu chuẩn nên đã cung cấp hỗ trợ thư viện để kiểm tra tràn số học. Bây giờ, làm thế nào một thói quen thư viện có thể phát sinh một hiệu suất nếu bạn không bao giờ sử dụng nó?
zvrba

5
Một tiêu cực lớn là GCC không có cờ để bắt tràn số nguyên đã ký và ném ngoại lệ thời gian chạy. Mặc dù có các cờ x86 để phát hiện các trường hợp như vậy, GCC không sử dụng chúng. Có một cờ như vậy sẽ cho phép các ứng dụng không quan trọng về hiệu năng (đặc biệt là di sản) mang lại lợi ích bảo mật mà không cần xem xét lại và tái cấu trúc mã.
Andrew Keeton

116

Thêm một mẹo của trình biên dịch GCC, nhưng bạn có thể đưa ra gợi ý về chỉ thị nhánh cho trình biên dịch (phổ biến trong nhân Linux)

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

xem: http://kerneltrap.org/node/4705

Những gì tôi thích về điều này là nó cũng thêm một số biểu cảm cho một số chức năng.

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

2
Thủ thuật này rất tuyệt ... :) Đặc biệt với các macro bạn xác định. :)
- Phục hồi Monica

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Đây là một mục tùy chọn trong tiêu chuẩn, nhưng nó phải là một tính năng ẩn, bởi vì mọi người liên tục định nghĩa lại chúng. Một cơ sở mã mà tôi đã làm việc (và hiện vẫn đang làm) có nhiều định nghĩa lại, tất cả đều có các định danh khác nhau. Hầu hết thời gian với các macro tiền xử lý:

#define INT16 short
#define INT32  long

Và như thế. Nó làm cho tôi muốn kéo tóc ra. Chỉ cần sử dụng typedefs số nguyên tiêu chuẩn kỳ dị!


3
Tôi nghĩ rằng họ là C99 hoặc như vậy. Tôi đã không tìm thấy một cách di động để đảm bảo những điều này sẽ được xung quanh.
akauppi

3
Chúng là một phần tùy chọn của C99, nhưng tôi biết không có nhà cung cấp trình biên dịch nào không thực hiện điều này.
Ben Collins

10
stdint.h không phải là tùy chọn trong C99, nhưng theo tiêu chuẩn C99 rõ ràng là dành cho một số nhà cung cấp ( ho Microsoft).
Ben Combee

5
@Pete, nếu bạn muốn làm hậu môn: (1) Chủ đề này không liên quan đến bất kỳ sản phẩm nào của Microsoft. (2) Chủ đề này không bao giờ có liên quan đến C ++. (3) Không có thứ nào như C ++ 97.
Ben Collins

5
Hãy xem azillionmonkeys.com/qed/pstdint.h - một stdint gần với di
động.h

73

Toán tử dấu phẩy không được sử dụng rộng rãi. Nó chắc chắn có thể bị lạm dụng, nhưng nó cũng có thể rất hữu ích. Việc sử dụng này là phổ biến nhất:

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

Nhưng bạn có thể sử dụng toán tử này ở bất cứ đâu. Quan sát:

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

Mỗi câu lệnh được ước tính, nhưng giá trị của biểu thức sẽ là giá trị của câu lệnh cuối cùng được ước tính.


7
Trong 20 năm CI không bao giờ thấy điều đó!
Martin Beckett

11
Trong C ++, bạn thậm chí có thể quá tải nó.
Wouter Lievens

6
có thể! = nên, tất nhiên. Điều nguy hiểm với quá tải là việc tích hợp sẵn áp dụng cho mọi thứ đã có, bao gồm cả khoảng trống, do đó sẽ không bao giờ thất bại trong việc biên dịch vì thiếu quá tải có sẵn. Tức là, cho lập trình viên nhiều dây.
Aaron

Int bên trong vòng lặp sẽ không hoạt động với C: đó là một ứng dụng C ++. Là "," hoạt động tương tự như cho (i = 0, j = 10; i <j; j--, i ++)?
Aif

63

khởi tạo cấu trúc về không

struct mystruct a = {0};

điều này sẽ bằng không tất cả các yếu tố cấu trúc.


2
Nó không bằng không, nếu có, tuy nhiên.
Mikeage

2
@simonn, không, nó không thực hiện hành vi không xác định nếu cấu trúc có chứa các loại không tách rời. bộ nhớ với 0 trên bộ nhớ của float / double vẫn sẽ bằng 0 khi bạn diễn giải float / double (float / double được thiết kế như thế trên mục đích).
Trevor Boyd Smith

6
@Andrew: memset/ callocdo "all byte zero" (nghĩa là các số 0 vật lý), thực sự không được xác định cho tất cả các loại. { 0 } được đảm bảo để giới thiệu mọi thứ với các giá trị logic hợp lý . Các con trỏ, chẳng hạn, được đảm bảo để có được các giá trị null thích hợp của chúng, ngay cả khi giá trị null trên nền tảng đã cho là 0xBAADFOOD.
AnT

1
@nvl: Bạn nhận được số 0 vật lý khi bạn chỉ cần thiết lập mạnh mẽ tất cả bộ nhớ bị chiếm bởi đối tượng thành trạng thái all-bits-zero. Đây là những gì memsetkhông (với 0đối số thứ hai). Bạn nhận được số 0 logic khi bạn khởi tạo / gán 0(hoặc { 0 }) cho đối tượng trong mã nguồn. Hai loại số không không nhất thiết tạo ra cùng một kết quả. Như trong ví dụ với con trỏ. Khi bạn làm memsettrên một con trỏ, bạn nhận được một 0x0000con trỏ. Nhưng khi bạn gán 0cho một con trỏ, bạn sẽ nhận được giá trị con trỏ null , ở mức vật lý có thể là 0xBAADF00Dhoặc bất cứ thứ gì khác.
AnT

3
@nvl: Chà, trong thực tế, sự khác biệt thường chỉ mang tính khái niệm. Nhưng trên lý thuyết, hầu như bất kỳ loại nào cũng có thể có nó. Ví dụ , double. Thông thường, nó được thực hiện theo tiêu chuẩn IEEE-754, trong đó số 0 logic và số 0 vật lý là như nhau. Nhưng ngôn ngữ không yêu cầu IEEE-754. Vì vậy, có thể xảy ra rằng khi bạn làm double d = 0;(số 0 logic), một số bit trong bộ nhớ chiếm dụng dsẽ không bằng không.
AnT

52

Hằng số nhiều ký tự:

int x = 'ABCD';

Điều này đặt xthành 0x41424344(hoặc 0x44434241, tùy thuộc vào kiến ​​trúc).

EDIT: Kỹ thuật này không khả dụng, đặc biệt nếu bạn tuần tự hóa int. Tuy nhiên, nó có thể cực kỳ hữu ích để tạo ra các enum tự ghi lại. ví dụ

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

Điều này làm cho nó đơn giản hơn nhiều nếu bạn đang xem một bãi chứa bộ nhớ thô và cần xác định giá trị của một enum mà không cần phải tìm kiếm nó.


Tôi khá chắc chắn đây không phải là một cấu trúc di động. Kết quả của việc tạo hằng số đa ký tự được xác định theo thực hiện.
Đánh dấu Bessey

8
Các ý kiến ​​"không di động" bỏ lỡ điểm hoàn toàn. Nó giống như chỉ trích một chương trình sử dụng INT_MAX chỉ vì INT_MAX "không di động" :) Tính năng này có tính di động như cần thiết. Hằng số đa char là một tính năng cực kỳ hữu ích cung cấp cách dễ đọc để tạo ID số nguyên duy nhất.
AnT

1
@Chris Lutz - Tôi khá chắc chắn dấu phẩy kéo dài trở lại K & R. Nó được mô tả trong phiên bản thứ hai (1988).
Ferruccio

1
@Ferruccio: Bạn phải suy nghĩ về dấu phẩy trong danh sách trình khởi tạo tổng hợp. Đối với dấu phẩy trong khai báo enum - đó là một bổ sung gần đây, C99.
AnT

3
Bạn đã quên 'HANG' hoặc 'BSOD' :-)
JBRWilkinson

44

Tôi không bao giờ sử dụng các trường bit nhưng chúng có vẻ tuyệt vời cho những thứ cực thấp.

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

Điều này có nghĩa là sizeof(cat)có thể nhỏ như sizeof(char).


Bình luận kết hợp của Aaronleppie , cảm ơn các bạn.


Sự kết hợp giữa cấu trúc và công đoàn thậm chí còn thú vị hơn - trên các hệ thống nhúng hoặc mã trình điều khiển cấp thấp. Một ví dụ là khi bạn muốn phân tích các thanh ghi của thẻ SD, bạn có thể đọc nó bằng cách sử dụng union (1) và đọc nó ra bằng union (2) là một cấu trúc của bitfield.
ComSubVie

5
Bitfield không thể di động - trình biên dịch có thể tự do lựa chọn, trong ví dụ của bạn, các chân sẽ được phân bổ 3 bit quan trọng nhất hoặc 3 bit có ý nghĩa ít nhất.
zvrba

3
Bitfield là một ví dụ về việc tiêu chuẩn mang lại cho việc triển khai rất nhiều sự tự do trong cách chúng được thực hiện, trong thực tế, chúng gần như vô dụng. Nếu bạn quan tâm có bao nhiêu bit mà một giá trị chiếm giữ và cách nó được lưu trữ, tốt hơn hết bạn nên sử dụng bitmasks.
Đánh dấu Bessey

26
Bitfield thực sự có khả năng di động miễn là bạn coi chúng là các thành phần cấu trúc, chứ không phải là "các số nguyên". Kích thước, không phải vị trí, là vấn đề trong một hệ thống nhúng với bộ nhớ hạn chế, vì mỗi bit là quý giá ... nhưng hầu hết các lập trình viên ngày nay còn quá trẻ để nhớ điều đó. :-)
Adam Liss

5
@Adam: vị trí có thể quan trọng trong một hệ thống nhúng (hoặc ở nơi khác), nếu bạn phụ thuộc vào vị trí của bitfield trong byte của nó. Sử dụng mặt nạ loại bỏ bất kỳ sự mơ hồ. Tương tự cho các công đoàn.
Steve Melnikoff

37

C có một trình biên dịch chuẩn nhưng không phải tất cả các trình biên dịch C đều tuân thủ đầy đủ (Tôi chưa thấy trình biên dịch C99 tuân thủ đầy đủ nào!).

Điều đó nói rằng, các thủ thuật tôi thích là những thủ thuật không rõ ràng và có thể di động trên các nền tảng vì chúng dựa vào ngữ nghĩa C. Chúng thường là về macro hoặc số học bit.

Ví dụ: hoán đổi hai số nguyên không dấu mà không sử dụng biến tạm thời:

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

hoặc "mở rộng C" để thể hiện các máy trạng thái hữu hạn như:

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

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

có thể đạt được với các macro sau:

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

Tuy nhiên, nói chung, tôi không thích các thủ thuật thông minh nhưng làm cho mã trở nên phức tạp không cần thiết để đọc (như ví dụ hoán đổi) và tôi thích các thủ thuật làm cho mã rõ ràng hơn và truyền đạt trực tiếp ý định (như ví dụ về FSM) .


18
C hỗ trợ xích, vì vậy bạn có thể thực hiện a ^ = b ^ = a ^ = b;
OJ.

4
Nói một cách chính xác, ví dụ trạng thái là một dấu tích của bộ tiền xử lý chứ không phải ngôn ngữ C - có thể sử dụng cái trước mà không cần cái sau.
Greg Whitfield

15
OJ: thực sự những gì bạn đề xuất là hành vi không xác định do quy tắc điểm chuỗi. Nó có thể hoạt động trên hầu hết các trình biên dịch, nhưng không chính xác hoặc di động.
Evan Teran

5
Trao đổi Xor thực sự có thể kém hiệu quả hơn trong trường hợp đăng ký miễn phí. Bất kỳ tối ưu hóa tốt sẽ làm cho biến temp là một thanh ghi. Tùy thuộc vào việc triển khai (và cần hỗ trợ song song), trao đổi thực sự có thể sử dụng bộ nhớ thực thay vì một thanh ghi (sẽ giống nhau).
Paul de Vrieze

27
xin đừng bao giờ thực sự làm điều này: vi.wikipedia.org/wiki/ Kẻ
Christian Oudard

37

Các cấu trúc xen kẽ như Thiết bị của 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, bất cứ ai sử dụng Thiết bị của Duff đều là một người thích kịch bản đã xem Thiết bị của Duff và nghĩ rằng mã của họ sẽ trông 1337 nếu họ sử dụng Thiết bị của Duff. (1.) Thiết bị của Duff không cung cấp bất kỳ sự tăng hiệu suất nào trên bộ xử lý hiện đại vì bộ xử lý hiện đại có vòng lặp không phí. Nói cách khác, nó là một đoạn mã lỗi thời. (2.) Ngay cả khi bộ xử lý của bạn không cung cấp vòng lặp không có phí, nó có thể sẽ có một cái gì đó giống như xử lý véc tơ SSE / altivec / vector sẽ khiến Thiết bị của Duff của bạn xấu hổ khi bạn sử dụng memcpy (). (3.) Tôi có đề cập đến việc người khác làm memcpy () duff không hữu ích không?
Trevor Boyd Smith

2
@ComSubVie, vui lòng gặp Fist-of-death của tôi ( en.wikipedia.org/wiki/iêu )
Trevor Boyd Smith

12
@Trevor: vậy chỉ có chương trình kiddies script 8051 và vi điều khiển PIC, phải không?
SF.

6
@Trevor Boyd Smith: Mặc dù Thiết bị của Duff có vẻ lỗi thời, nhưng nó vẫn là một sự tò mò lịch sử, xác nhận câu trả lời của ComSubVie. Dù sao, trích dẫn Wikipedia: "Khi nhiều phiên bản thiết bị của Duff bị xóa khỏi Máy chủ XFree86 trong phiên bản 4.0, đã có một sự cải thiện đáng chú ý về hiệu suất." ...
paercebal

2
Trên Symbian, chúng tôi đã từng đánh giá các vòng lặp khác nhau để mã hóa pixel nhanh; thiết bị của duff, trong trình biên dịch chương trình, là nhanh nhất. Vì vậy, nó vẫn có liên quan đến các lõi ARM chính trên điện thoại thông minh của bạn ngày nay.
Sẽ

33

Tôi rất thích các trình khởi tạo được chỉ định, được thêm vào C99 (và được hỗ trợ trong gcc trong một thời gian dài):

#define FOO 16
#define BAR 3

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

Việc khởi tạo mảng không còn phụ thuộc vào vị trí. Nếu bạn thay đổi các giá trị của FOO hoặc BAR, việc khởi tạo mảng sẽ tự động tương ứng với giá trị mới của chúng.


Cú pháp gcc đã hỗ trợ trong một thời gian dài không giống với cú pháp C99 tiêu chuẩn.
Mark Baker

28

C99 có một số khởi tạo cấu trúc bất kỳ thứ tự tuyệt vời.

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

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


27

cấu trúc ẩn danh và mảng là một yêu thích của tôi. (xem http://www.run.montefiore.ulg.ac.be/~martin/resource/kung-f00.html )

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

hoặc là

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

nó thậm chí có thể được sử dụng để cung cấp danh sách liên kết ...


3
Tính năng này thường được gọi là "chữ ghép". Các cấu trúc ẩn danh (hoặc chưa được đặt tên) chỉ định các cấu trúc lồng nhau không có tên thành viên.
calandoa

theo GCC của tôi, "ISO C90 cấm các chữ ghép".
jmtd

"ISO C99 hỗ trợ chữ ghép." "Là một phần mở rộng, GCC hỗ trợ các chữ ghép trong chế độ C89 và trong C ++" (thông tin dixit gcc). Thêm vào đó, "Là một phần mở rộng GNU, GCC cho phép khởi tạo các đối tượng có thời lượng lưu trữ tĩnh theo nghĩa đen (không thể có trong ISO C99, vì trình khởi tạo không phải là hằng số)."
PypeBros

24

gcc có một số phần mở rộng cho ngôn ngữ C mà tôi thích, có thể tìm thấy ở đây . Một số mục yêu thích của tôi là thuộc tính chức năng . Một ví dụ cực kỳ hữu ích là thuộc tính định dạng. Điều này có thể được sử dụng nếu bạn xác định một chức năng tùy chỉnh có chuỗi định dạng printf. Nếu bạn bật thuộc tính hàm này, gcc sẽ kiểm tra các đối số của bạn để đảm bảo rằng chuỗi định dạng và đối số của bạn khớp với nhau và sẽ tạo ra các cảnh báo hoặc lỗi khi thích hợp.

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

24

tính năng (ẩn) đã "gây sốc" cho tôi khi tôi lần đầu tiên nhìn thấy là về printf. tính năng này cho phép bạn sử dụng các biến để định dạng định dạng định dạng. tìm mã, bạn sẽ thấy tốt hơn:

#include <stdio.h>

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

nhân vật * đạt được hiệu ứng này.


24

Chà ... tôi nghĩ rằng một trong những điểm mạnh của ngôn ngữ C là tính di động và tính chuẩn của nó, vì vậy bất cứ khi nào tôi tìm thấy một "mánh khóe ẩn giấu" nào đó trong triển khai tôi hiện đang sử dụng, tôi cố gắng không sử dụng nó vì tôi cố gắng giữ Mã C là tiêu chuẩn và di động càng tốt.


Nhưng trong thực tế, bạn có thường xuyên phải biên dịch mã của mình với một trình biên dịch khác không?
Joe D

3
@Joe D nếu đó là một dự án đa nền tảng như Windows / OSX / Linux, có thể là một chút, và cũng có các vòm khác nhau như x86 so với x86_64 và v.v ...
Pharaun

@JoeD Trừ khi bạn ở trong một dự án rất hẹp, rất vui khi kết hôn với một nhà cung cấp trình biên dịch, rất. Bạn có thể muốn tránh thực sự phải chuyển đổi trình biên dịch, nhưng bạn muốn giữ tùy chọn đó mở. Tuy nhiên, với các hệ thống nhúng, bạn không phải lúc nào cũng có được sự lựa chọn. AHS, ASS.
XtL

19

Biên dịch xác nhận thời gian, như đã thảo luận ở đây .

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

Nối chuỗi liên tục

Tôi khá ngạc nhiên khi không thấy nó sẵn sàng trong các câu trả lời, vì tất cả các trình biên dịch mà tôi biết đều hỗ trợ nó, nhưng nhiều lập trình viên dường như bỏ qua nó. Đôi khi nó thực sự tiện dụng và không chỉ khi viết macro.

Trường hợp sử dụng tôi có trong mã hiện tại của mình: Tôi có một #define PATH "/some/path/"tệp cấu hình (thực sự nó được giải quyết bởi tệp thực hiện). Bây giờ tôi muốn xây dựng đường dẫn đầy đủ bao gồm tên tệp để mở nguồn. Nó chỉ đi đến:

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

Thay vì kinh khủng, nhưng rất phổ biến:

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

Lưu ý rằng giải pháp khủng khiếp phổ biến là:

  • dài gấp ba lần
  • ít dễ đọc hơn
  • chậm hơn nhiều
  • ít mạnh mẽ hơn khi nó được đặt thành giới hạn kích thước bộ đệm tùy ý (nhưng bạn sẽ phải sử dụng mã dài hơn để tránh điều đó mà không có sự liên kết chuỗi liên tục).
  • sử dụng nhiều không gian ngăn xếp

1
Nó cũng hữu ích để phân chia một chuỗi hằng số trên nhiều dòng nguồn mà không sử dụng `\` bẩn.
heo

15

Chà, tôi chưa bao giờ sử dụng nó, và tôi không chắc liệu tôi có từng giới thiệu nó cho bất kỳ ai không, nhưng tôi cảm thấy câu hỏi này sẽ không đầy đủ nếu không đề cập đến thủ thuật đồng quy của Simon Tatham .


12

Khi khởi tạo mảng hoặc enum, bạn có thể đặt dấu phẩy sau mục cuối cùng trong danh sách khởi tạo. ví dụ:

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

enum foo { bar, baz, boom, };

Điều này đã được thực hiện để nếu bạn tự động tạo mã, bạn không cần phải lo lắng về việc loại bỏ dấu phẩy cuối cùng.


Điều này cũng quan trọng trong môi trường nhiều nhà phát triển, ví dụ, Eric thêm vào "baz", và sau đó George thêm vào "boom". Nếu Eric quyết định rút mã của mình ra để xây dựng dự án tiếp theo, nó vẫn sẽ biên dịch với sự thay đổi của George. Rất quan trọng đối với việc kiểm soát mã nguồn đa nhánh và lịch trình phát triển chồng chéo.
Harold Bamford

Enums có thể là C99. Bộ khởi tạo mảng & dấu phẩy là K & R.
Ferruccio

Đồng bằng enum là trong c89, AFAIK. Ít nhất là họ đã có mặt từ lâu.
XtL

12

Phân công cấu trúc là mát mẻ. Nhiều người dường như không nhận ra rằng các cấu trúc cũng là các giá trị và có thể được chỉ định xung quanh, không cần sử dụng memcpy(), khi một phép gán đơn giản thực hiện thủ thuật.

Ví dụ, hãy xem xét một số thư viện đồ họa 2D tưởng tượng, nó có thể xác định một loại để thể hiện tọa độ màn hình (số nguyên):

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

Bây giờ, bạn làm những việc có thể trông "sai", như viết một hàm tạo một điểm được khởi tạo từ các đối số của hàm và trả về nó, như vậy:

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

Điều này là an toàn, miễn là (tất nhiên) khi giá trị trả về được sao chép theo giá trị bằng cách sử dụng phép gán struct:

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

Theo cách này, bạn có thể viết mã ish khá sạch và hướng đối tượng, tất cả đều theo tiêu chuẩn C.


4
Tất nhiên, có những tác động về hiệu suất để vượt qua các cấu trúc lớn theo cách này; nó thường hữu ích (và thực sự là điều mà nhiều người không nhận ra bạn có thể làm) nhưng bạn cần xem xét liệu việc chuyển con trỏ có tốt hơn không.
Mark Baker

1
Tất nhiên, có thể có . Ít hơn cũng có thể cho trình biên dịch phát hiện việc sử dụng và tối ưu hóa nó.
thư giãn

Hãy cẩn thận nếu bất kỳ yếu tố nào là con trỏ, vì bạn sẽ sao chép chính con trỏ, không phải nội dung của chúng. Tất nhiên, điều tương tự cũng đúng nếu bạn sử dụng memcpy ().
Adam Liss

Trình biên dịch không thể tối ưu hóa việc chuyển đổi theo giá trị này bằng cách chuyển qua tham chiếu, trừ khi nó có thể thực hiện tối ưu hóa toàn cầu.
Blaisorblade

Có lẽ đáng lưu ý rằng trong C ++, tiêu chuẩn đặc biệt cho phép tối ưu hóa bản sao (tiêu chuẩn phải cho phép trình biên dịch thực hiện vì nó có nghĩa là trình tạo sao chép có thể không được gọi là tác dụng phụ) và vì hầu hết các trình biên dịch C ++ cũng là trình biên dịch C, rất có thể trình biên dịch của bạn thực hiện việc tối ưu hóa này.
Joseph Garvin

10

Lập chỉ mục vector lạ:

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

4
Nó thậm chí còn tốt hơn ... char c = 2 ["Xin chào"]; (c == 'l' sau này).
YRP

5
Không có gì lạ khi bạn xem xét rằng v [index] == * (v + index) và index [v] == * (index + v)
Ferruccio

17
Xin vui lòng cho tôi biết bạn không thực sự sử dụng "tất cả thời gian" này, như câu hỏi yêu cầu!
Thử

9

Trình biên dịch C thực hiện một trong một số tiêu chuẩn. Tuy nhiên, có một tiêu chuẩn không có nghĩa là tất cả các khía cạnh của ngôn ngữ được xác định. Ví dụ, thiết bị của Duff là một tính năng 'ẩn' yêu thích đã trở nên phổ biến đến mức các trình biên dịch hiện đại có mã nhận dạng mục đích đặc biệt để đảm bảo rằng các kỹ thuật tối ưu hóa không làm mờ hiệu ứng mong muốn của mẫu thường được sử dụng này.

Nói chung, các tính năng ẩn hoặc thủ thuật ngôn ngữ không được khuyến khích khi bạn đang chạy trên cạnh dao cạo của bất kỳ (các) tiêu chuẩn C nào mà trình biên dịch của bạn sử dụng. Nhiều thủ thuật như vậy không hoạt động từ trình biên dịch này sang trình biên dịch khác và thường các loại tính năng này sẽ thất bại từ một phiên bản của bộ trình biên dịch bởi một nhà sản xuất nhất định sang phiên bản khác.

Nhiều thủ thuật đã phá vỡ mã C bao gồm:

  1. Dựa vào cách trình biên dịch đưa ra các cấu trúc trong bộ nhớ.
  2. Giả định về tuổi thọ của số nguyên / số float.
  3. Giả định về chức năng ABI.
  4. Giả định về hướng mà khung xếp chồng phát triển.
  5. Giả định về thứ tự thực hiện trong các báo cáo.
  6. Giả định về thứ tự thực hiện các câu lệnh trong các đối số hàm.
  7. Giả định về kích thước bit hoặc độ chính xác của các loại ngắn, int, long, float và double.

Các vấn đề và vấn đề khác phát sinh bất cứ khi nào lập trình viên đưa ra các giả định về các mô hình thực thi, tất cả được chỉ định trong hầu hết các tiêu chuẩn C là hành vi 'phụ thuộc vào trình biên dịch'.


Để giải quyết hầu hết những điều đó, hãy đặt những giả định đó phụ thuộc vào đặc điểm của nền tảng của bạn và mô tả từng nền tảng trong tiêu đề của riêng mình. Thực hiện đơn hàng là một ngoại lệ - không bao giờ dựa vào đó; trên các ý tưởng khác, mỗi nền tảng cần có một quyết định đáng tin cậy.
Blaisorblade

2
@Blaisorblade, Thậm chí tốt hơn, sử dụng các xác nhận thời gian biên dịch để ghi lại các giả định của bạn theo cách sẽ khiến trình biên dịch thất bại trên nền tảng nơi chúng bị vi phạm.
RBerteig

Tôi nghĩ người ta nên kết hợp cả hai, để mã của bạn hoạt động trên nhiều nền tảng (đó là ý định ban đầu) và nếu các macro tính năng được đặt sai cách, các xác nhận thời gian biên dịch sẽ bắt được nó. Tôi không chắc chắn, giả sử, giả định về chức năng ABI có thể kiểm tra được dưới dạng xác nhận thời gian biên dịch, nhưng có thể áp dụng cho hầu hết các giả định (hợp lệ) khác (ngoại trừ thứ tự thực hiện ;-)).
Blaisorblade

Kiểm tra chức năng ABI nên được xử lý bởi một bộ kiểm tra.
heo

9

Khi sử dụng sscanf, bạn có thể sử dụng% n để tìm nơi bạn nên tiếp tục đọc:

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

Rõ ràng, bạn không thể thêm câu trả lời khác, vì vậy tôi sẽ đưa câu trả lời thứ hai vào đây, bạn có thể sử dụng "&&" và "||" như điều kiện:

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

Mã này sẽ xuất ra:

Chào
ROFL

8

sử dụng INT (3) để đặt điểm dừng tại mã là sở thích của tôi


3
Tôi không nghĩ nó di động. Nó sẽ hoạt động trên x86, nhưng các nền tảng khác thì sao?
Cristian Ciupitu

1
Tôi không biết - Bạn nên đăng câu hỏi về nó
Dror Helper

2
Đây là một kỹ thuật tốt và cụ thể là X86 (mặc dù có thể có các kỹ thuật tương tự trên các nền tảng khác). Tuy nhiên, đây không phải là một tính năng của C. Nó phụ thuộc vào các phần mở rộng C không chuẩn hoặc các cuộc gọi thư viện.
Ferruccio

1
Trong GCC có __builtin_trap và cho MSVC __debugbreak sẽ hoạt động trên mọi kiến ​​trúc được hỗ trợ.
Axel Gneiting

8

Tính năng "ẩn" yêu thích của tôi về C, là việc sử dụng% n trong printf để ghi lại vào ngăn xếp. Thông thường printf bật các giá trị tham số từ ngăn xếp dựa trên chuỗi định dạng, nhưng% n có thể ghi lại chúng.

Kiểm tra phần 3.4.2 tại đây . Có thể dẫn đến rất nhiều lỗ hổng khó chịu.


liên kết không hoạt động nữa, trên thực tế trang web dường như không hoạt động. Bạn có thể cung cấp một liên kết khác?
thequark

@thequark: Bất kỳ bài viết nào về "lỗ hổng chuỗi định dạng" sẽ có một số thông tin trong đó .. (ví dụ: crypto.stanford.edu/cs155/ con /formatopes-1.2.pdf ) .. Tuy nhiên do tính chất của trường, tính bảo mật bản thân các trang web là một chút dễ vỡ và các bài báo học thuật thực sự rất khó để có được (với việc thực hiện).
Sridhar Iyer

8

Kiểm tra giả định thời gian biên dịch bằng enums: Ví dụ ngu ngốc, nhưng có thể thực sự hữu ích cho các thư viện có hằng số cấu hình thời gian biên dịch.

#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 gọn gàng. Tôi đã từng sử dụng macro CompilerAssert từ Microsoft, nhưng của bạn cũng không tệ. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter

1
Tôi thích phương pháp liệt kê. Cách tiếp cận mà tôi đã sử dụng trước đây đã tận dụng việc loại bỏ mã chết: "if (Something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" không có lỗi cho đến thời gian liên kết, mặc dù nó có ưu điểm là cho phép lập trình viên biết thông qua lỗi thông báo rằng blorg đã được woozled.
supercat

8

Gcc (c) có một số tính năng thú vị mà bạn có thể kích hoạt, chẳng hạn như khai báo hàm lồng nhau và dạng a ?: B của toán tử ?:, Trả về a nếu a không sai.


8

Tôi phát hiện ra gần đây 0 bitfield.

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

Nó sẽ cho một bố cục của

000aaabb 0ccccddd

thay vì không có: 0;

0000aaab bccccddd

Trường độ rộng 0 cho biết các bitfield sau phải được đặt trên thực thể nguyên tử tiếp theo ( char)


7

Các macro đối số biến kiểu C99, aka

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

mà sẽ được sử dụng như

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

Ở đây tôi cũng sử dụng toán tử chuỗi và nối chuỗi liên tục, các tính năng khác tôi thực sự thích.


Bạn có thêm 'R' trong VA_ARGS .
Blaisorblade

6

Biến tự động kích thước biến cũng hữu ích trong một số trường hợp. Chúng đã được thêm i nC99 và đã được hỗ trợ trong gcc trong một thời gian dài.

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

Bạn kết thúc với một bộ đệm trên ngăn xếp có chỗ cho tiêu đề giao thức kích thước cố định cộng với dữ liệu kích thước thay đổi. Bạn có thể có được hiệu ứng tương tự với alloca (), nhưng cú pháp này nhỏ gọn hơn.

Bạn phải chắc chắn rằng ExtraPadding là một giá trị hợp lý trước khi gọi thủ tục này, hoặc cuối cùng bạn sẽ thổi tung ngăn xếp. Bạn phải tỉnh táo kiểm tra các đối số trước khi gọi malloc hoặc bất kỳ kỹ thuật cấp phát bộ nhớ nào khác, vì vậy điều này thực sự không bình thường.


Điều này cũng sẽ hoạt động chính xác nếu một byte / char không chính xác rộng 8 bit trên nền tảng đích? Tôi biết, những trường hợp đó rất hiếm, nhưng vẫn ... :)
Stephan202
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.