2013-10-10 13 views
6

Thông thường, ngoài việc cung cấp một khai báo chức năng, tiêu đề C tiêu chuẩn có thể cung cấp một "mặt nạ vĩ mô" để làm cho mọi thứ nhanh hơn. Ví dụ: nếu tôi bao gồm ctype.h, tệp tiêu đề sẽ khai báoCó bất kỳ hạn chế nào đối với tiêu chuẩn C cho phép các chức năng được triển khai dưới dạng macro không?

int isdigit(int c); 

Nhưng cũng có thể che dấu khai báo bằng macro. Tôi tin rằng đây là một di isdigit vĩ mô theo tiêu chuẩn C:

#define isdigit(c) ((c) >= '0' && (c) <= '9') 

Tất nhiên, macro này cũng rất nguy hiểm vì nó giới thiệu hành vi undefined nếu bạn làm điều này trong khi vĩ mô được xác định:

int c = 'A'; 
printf("%d\n", isdigit(c++)); 

Để tránh UB trong trường hợp giả định này, tôi phải bao quanh tên hàm với các số parens: (isdigit)(c++). Vì vậy, câu hỏi của tôi là: có bất kỳ hạn chế nào đối với loại macro mặt nạ nào mà tiêu đề chuẩn có thể xác định không? Chúng có được đảm bảo không gây ra hành vi không xác định nếu một biểu thức đối số có tác dụng phụ hay chúng được cho phép về mặt kỹ thuật có hành vi kỳ lạ như chúng ta thấy ở trên không? Các giới hạn ở đâu?

+0

Macro được đề xuất của bạn không phù hợp. Với các ngoại lệ của việc triển khai macro (tùy chọn) của các hàm 'getc()' và 'putc()', một định nghĩa macro của hàm chuẩn có thể không đánh giá bất kỳ đối số nào của nó nhiều hơn một lần. Nếu được gọi là 'isdigit (C++)' hành vi macro của bạn, nhưng câu lệnh hoạt động đúng với các macro tuân thủ và với một hàm. –

+0

Xin lỗi, ý bạn là gì bởi "macro chuẩn"? Trong mọi trường hợp, khi tôi nói "di động", tôi có nghĩa là "cư xử tốt trên tất cả các trình biên dịch C tuân thủ tiêu chuẩn", khi thông qua một đối số thích hợp mà không có tác dụng phụ. –

+0

Tiêu chuẩn C quy định rằng bất kỳ chức năng chuẩn nào cũng có thể được thực hiện dưới dạng macro. Nhưng nó cũng yêu cầu bất kỳ việc thực hiện macro nào của bất kỳ hàm C chuẩn nào (trừ 'getc()' và 'putc()') chỉ phải đánh giá các đối số của nó một lần, vì macro phải hoạt động chính xác như thể nó là một hàm. Hơn nữa, mọi hàm phải được khai báo và triển khai thực hiện như một hàm để địa chỉ của nó có thể được lấy và truyền xung quanh như một con trỏ đến hàm. Đó là khá nhiều thông tin mà Paul Griffiths đã trích dẫn bạn từ tiêu chuẩn. Lưu ý rằng 'setjmp()' rõ ràng là một macro. –

Trả lời

5

mỗi C11 7.1.4.1, "Sử dụng hàm thư viện", đặc biệt là phần cuối cùng được trích dẫn dưới đây:

Bất kỳ chức năng tuyên bố trong một tiêu đề có thể được thực hiện bổ sung như một macro chức năng giống như định nghĩa trong tiêu đề, do đó, nếu chức năng thư viện được khai báo rõ ràng khi tiêu đề của nó được bao gồm, một trong các kỹ thuật được hiển thị bên dưới có thể được sử dụng để đảm bảo việc khai báo không phải là bị ảnh hưởng bởi macro như vậy .... Vì cùng một lý do cú pháp, là được phép lấy địa chỉ của chức năng thư viện ngay cả khi nó cũng là được định nghĩa là ma cro. Việc sử dụng #undef để xóa bất kỳ định nghĩa macro nào cũng sẽ đảm bảo rằng chức năng thực tế được đề cập đến. Mọi yêu cầu gọi hàm thư viện được triển khai dưới dạng macro sẽ mở rộng đến mã đánh giá từng đối số chính xác một lần, được bảo vệ đầy đủ bằng dấu ngoặc đơn khi cần thiết, vì vậy thường là an toàn để sử dụng các biểu thức tùy ý làm đối số.

Lưu ý rằng "thường" ở cuối là quan trọng, ở đó và có ngoại lệ rõ ràng. C11 7.21.7.5.2 nói "chức năng getc tương đương với fgetc, ngoại trừ nếu nó được thực hiện dưới dạng macro, nó có thể đánh giá stream nhiều lần, vì vậy đối số không bao giờ là biểu thức có tác dụng phụ", với ngôn ngữ tương tự cho putc trong C11 7.21.7.7.2. Trong trường hợp cụ thể này, stream là một số FILE *, vì vậy trong những trường hợp bình thường, nó sẽ là loại kỳ lạ để có điều này như là một biểu hiện với tác dụng phụ, nhưng nó có thể xảy ra. Điều này cũng đúng đối với các đối tác nhân vật rộng, putwc()getwc(). Tôi không biết về bất kỳ trường hợp ngoại lệ nào khác như thế này.

+0

Thú vị - Tôi tự hỏi nếu các yêu cầu getc() và putc() đã thực sự thay đổi trong C11 (hoặc C99 cho vấn đề đó) ... xem câu trả lời của tôi. –

+0

Tôi nghĩ rằng "nói chung" là quan trọng trong câu cuối cùng. Mỗi C11 7.21.7.5.2, "hàm' getc' tương đương với 'fgetc', ngoại trừ nếu nó được thực thi dưới dạng macro, nó có thể đánh giá' luồng' nhiều hơn một lần, vì vậy đối số sẽ không bao giờ là biểu thức tác dụng phụ ", vì vậy tôi nghĩ rằng bạn vẫn còn chính xác. –

+2

Các ngoại lệ 'getc()' và 'putc()' xuất hiện trong C89 (và C99) cũng như C11. Mục tiêu là cho phép triển khai macro thường tránh bất kỳ cuộc gọi chức năng nào, nhưng các macro như vậy sẽ khó tránh khỏi việc đánh giá đối số luồng tệp nhiều lần. GCC cung cấp 'các câu lệnh biểu hiện' như một cách để có tất cả; tuy nhiên, chúng là phần mở rộng GCC, không phải là tính năng trình biên dịch C chuẩn. –

1

Từ đây: http://www.qnx.com/developers/docs/6.5.0/index.jsp?topic=%2Fcom.qnx.doc.dinkum_en_ecpp%2Flib_over.html

luận cứ rằng có tác dụng phụ đánh giá một cách như nhau cho dù biểu thức thực hiện việc mở rộng vĩ mô hoặc gọi hàm. Các macro cho các hàm getc và putc là các ngoại lệ rõ ràng đối với quy tắc này.

Tôi đã tìm thấy một vài tài liệu tham khảo khác nói về cùng một điều (mặc dù không có gì trích dẫn trực tiếp tiêu chuẩn C).

Vì vậy, có vẻ như giới hạn là chỉ getc()putc() có thể gây ra sự tàn phá theo cách bạn thấy trước. Ngoài hai, bạn nên an toàn, giả sử nền tảng của bạn ít nhất là một trong những sane hoặc phù hợp.

Các vấn đề liên quan