2009-10-28 29 views
11

Trong một trước question những gì tôi nghĩ được một câu trả lời tốt được bình chọn xuống cho việc sử dụng gợi ý của macroKhi nào bạn nên sử dụng các macro thay vì các hàm nội dòng?

#define radian2degree(a) (a * 57.295779513082) 
#define degree2radian(a) (a * 0.017453292519) 

thay vì chức năng inline. Xin vui lòng giải thích câu hỏi newbie, nhưng những gì là như vậy ác về macro trong trường hợp này?

+9

Trong trường hợp này, level2radian (45 + a/2.0) không làm những gì bạn nghĩ. Luôn luôn ngoặc đơn mọi đối số trong việc mở rộng - tốt, hầu như luôn luôn; các ngoại lệ chính là khi bạn đang làm những việc như dán mã thông báo hoặc xâu chuỗi. –

+3

những gì Jonathan nói - một điều tồi tệ hơn nhìn thấy một định nghĩa vĩ mô đó là một nỗi đau trong ass để đọc vì tất cả các parens damn, là đi qua một định nghĩa vĩ mô mà không có tất cả những parens damn. –

+3

Ngoài ra còn có quy ước ALLCAPS cho các macro, ít nhất là đặt chúng vào không gian tên giả của riêng chúng. –

Trả lời

6

Có một vài điều nghiêm chỉnh về macro.

Chúng là xử lý văn bản và không bị nhiễu. Nếu bạn #define foo 1, thì việc sử dụng sau đó foo làm số nhận dạng sẽ không thành công. Điều này có thể dẫn đến lỗi biên dịch lẻ và lỗi thời gian chạy khó tìm.

Chúng không có đối số theo nghĩa thông thường. Bạn có thể viết một hàm sẽ nhận hai giá trị int và trả về giá trị tối đa, vì các đối số sẽ được đánh giá một lần và các giá trị được sử dụng sau đó. Bạn không thể viết macro để làm điều đó, bởi vì nó sẽ đánh giá ít nhất một đối số hai lần và không thành công với một cái gì đó như max(x++, --y).

Cũng có những cạm bẫy phổ biến. Thật khó để có được nhiều câu lệnh ngay trong chúng, và chúng đòi hỏi rất nhiều dấu ngoặc đơn thừa.

Trong trường hợp của bạn, bạn cần ngoặc:

#define radian2degree(a) (a * 57.295779513082) 

cần phải được

#define radian2degree(a) ((a) * 57.295779513082) 

và bạn vẫn đang giẫm lên bất cứ ai viết một hàm radian2degree trong một số phạm vi bên trong, tự tin rằng đó định nghĩa sẽ hoạt động trong phạm vi riêng của nó.

+1

Tôi luôn luôn tìm thấy những tuyên bố của "tính năng" x "là điều ác không bao giờ sử dụng nó" dường như ngụ ý rằng các lập trình viên vô cùng ngu ngốc. Đề xuất các macro được đặt tên theo cách tự xác định là macro khi được sử dụng: RADIAN2DEGREEmac hoặc tương tự. –

+5

"Tính năng X là tà ác" không có nghĩa là "không sử dụng tính năng X" và tôi không hoàn toàn nói rằng macro là bản chất độc ác. Quy ước toàn bộ mũ có giá trị, nhưng không giải quyết được tất cả các vấn đề. Cùng với sự ràng buộc chặt chẽ và các thủ thuật khác. Tôi khuyên bạn nên tránh các macro giống như chức năng nếu có thể, bởi vì thường có những cách tốt hơn để làm bất cứ điều gì bạn đang cố gắng làm. –

+1

@David Câu trả lời hay nhưng không trả lời được câu hỏi: "Khi nào bạn nên sử dụng các macro thay vì các hàm nội tuyến?" Có vẻ như bạn đã trả lời ngược lại: tại sao bạn không nên sử dụng macro. Nhưng khi nào thì "tốt hơn" để sử dụng macro thay vì một hàm? – styfle

1

nếu có thể, luôn sử dụng chức năng nội tuyến. Đây là những loại an toàn và không thể dễ dàng định nghĩa lại.

xác định có thể được định nghĩa lại không xác định và không có loại kiểm tra.

+1

Nhược điểm trong C (trái ngược với C++) là không phải tất cả trình biên dịch nhất thiết phải hỗ trợ chúng - hoặc mặc định cho một chế độ mà chúng được hỗ trợ. Chúng được thêm vào C99, nhưng một số trình biên dịch (ví dụ như MSVC) không tuân thủ C99. Tôi không biết liệu MSVC có hỗ trợ bit tiêu chuẩn này hay không - nó có thể vì nó cũng có hỗ trợ C++ ẩn xung quanh. Tuy nhiên, nó không có các tính năng C99 khác mà sẽ rất tốt đẹp để có. –

+2

Trình biên dịch thông minh sẽ bỏ qua chỉ thị 'nội tuyến 'và nội dòng hoặc không phải là cuộc gọi riêng lẻ cho biết. –

+0

@David +1, nội dòng là đề xuất thuần túy. – LB40

7

Đối với vĩ mô cụ thể này, nếu tôi sử dụng nó như sau:

int x=1; 
x = radian2degree(x); 
float y=1; 
y = radian2degree(y); 

sẽ không có kiểm tra kiểu, và x, y sẽ chứa các giá trị khác nhau.

Bên cạnh đó, đoạn mã sau

float x=1, y=2; 
float z = radian2degree(x+y); 

sẽ không làm những gì bạn nghĩ, vì nó sẽ dịch để

float z = x+y*0.017453292519; 

thay vì

float z = (x+y)+0.017453292519; 

mà là kết quả mong đợi.

Đây chỉ là một vài ví dụ cho hành vi sai trái mà các macro lạm dụng có thể có.

Sửa

bạn có thể thấy các cuộc thảo luận thêm về vấn đề này here

+3

Câu trả lời hay. Nó cũng đáng nói đến sự ô nhiễm không gian tên (ví dụ như tôi không còn có thể khai báo một biến cục bộ hoặc trường struct có tên 'radian2degree'). –

+0

Tôi không hiểu khiếu nại đầu tiên của bạn. x sẽ không được biến đổi thành dấu phẩy động bằng macro, nhưng bằng phép nhân. –

+1

điểm của tôi là cả hai cách sử dụng đều hợp lệ và sẽ biên dịch, nhưng bạn sẽ không thấy bất kỳ dấu hiệu nào cho điều này. macro của bạn _should_ chỉ chấp nhận số dấu phẩy động, nhưng nó sẽ chấp nhận bất kỳ thứ gì. – Amirshk

0

Macro là ác vì bạn có thể sẽ đi qua hơn một biến hoặc một vô hướng với nó và điều này có thể giải quyết trong một hành vi không mong muốn (xác định macro tối đa để xác định giá trị lớn nhất giữa a và b nhưng chuyển một ++ và b ++ sang macro và xem điều gì xảy ra).

0

Nếu chức năng của bạn sắp được gạch chân, không có sự khác biệt về hiệu suất giữa hàm và macro. Tuy nhiên, có một số khác biệt về khả năng sử dụng giữa một hàm và một macro, tất cả đều có lợi khi sử dụng một hàm.

Nếu bạn tạo macro chính xác, không có vấn đề gì. Nhưng nếu bạn sử dụng một hàm, trình biên dịch sẽ làm điều đó một cách chính xác cho bạn mọi lúc. Vì vậy, việc sử dụng một hàm làm cho việc viết mã xấu trở nên khó khăn hơn.

2

Macro thường bị lạm dụng và người ta có thể dễ dàng phạm sai lầm khi sử dụng chúng như được minh họa trong ví dụ của bạn. Lấy radian2degree biểu thức (1 + 1):

  • với vĩ mô nó sẽ mở rộng ra 1 + 1 * 57,29 ... = 58,29 ...
  • với một chức năng đó sẽ là những gì bạn muốn nó được, cụ thể là (1 + 1) * 57.29 ... = ...

Tổng quát hơn, macro là ác vì họ trông giống như chức năng để họ lừa bạn sử dụng chúng giống như chức năng nhưng họ có quy tắc tinh tế của riêng mình. Trong trường hợp này, cách chính xác sẽ là viết nó sẽ là (chú ý dấu ngoặc xung quanh a):

#define radian2degree(a) ((a) * 57.295779513082) 

Nhưng bạn nên dính vào các hàm nội tuyến. Xem các liên kết từ thư mục C++ FAQ Lite để biết thêm ví dụ về macro ác và sự tinh tế của họ:

+1

+1 nhưng a) macro không phải là "ác nói chung". Chúng có thể bị lạm dụng, nhưng chúng không phải là gốc rễ của tất cả những điều xấu xa, hơn bất kỳ thứ gì khác. b) Tại sao liên kết tới C++ FAQ cho câu hỏi C? Không có thẻ C++ và không có thông tin C++ cụ thể trong câu hỏi. –

+0

Bạn nói đúng về "điều ác nói chung", nó quá khắc nghiệt và chung chung. Tôi đã chỉnh sửa nó. Liên quan đến b), như tôi thấy tất cả thông tin trong các liên kết C++ FAQ mà tôi đưa ra cũng áp dụng cho C và tôi nghĩ rằng các vấn đề/tinh chỉnh của macro được giải thích rõ ràng ở đó. . Vậy tại sao không liên kết với C++ FAQ? –

1

tiền xử lý của trình biên dịch là một điều finnicky , và do đó một ứng cử viên khủng khiếp cho thủ đoạn thông minh. Như những người khác đã chỉ ra, thật dễ dàng để trình biên dịch hiểu sai ý định của bạn với macro và thật dễ dàng để bạn hiểu nhầm macro sẽ thực sự làm gì, nhưng quan trọng nhất, bạn không thể bước vào macro trong trình gỡ lỗi!

8

Hầu hết các câu trả lời khác đều thảo luận tại sao macro là ác bao gồm cách ví dụ của bạn có lỗ hổng sử dụng macro phổ biến. Dưới đây là Stroustrup's take: http://www.research.att.com/~bs/bs_faq2.html#macro

Nhưng câu hỏi của bạn là hỏi những macro nào vẫn tốt.Có một số điều mà macro là tốt hơn so với chức năng nội tuyến, và đó là nơi bạn đang làm những việc mà chỉ đơn giản là không thể được thực hiện với các chức năng nội tuyến, chẳng hạn như:

  • thẻ dán
  • đối phó với số dòng hoặc như vậy (như đối với việc tạo ra các thông báo lỗi trong assert())
  • đối phó với những thứ mà không phải là biểu thức (ví dụ có bao nhiêu hiện thực của offsetof() sử dụng sử dụng một loại tên để tạo ra một hoạt động cast)
  • vĩ mô để có được một số của các phần tử mảng (không thể làm điều đó với một hàm, vì tên mảng phân rã thành một con trỏ quá e asily)
  • tạo 'kiểu đa hình' điều chức năng giống như trong C mà mẫu không có sẵn

Nhưng với một ngôn ngữ có chức năng nội tuyến, những ứng dụng phổ biến hơn các macro không cần thiết. Tôi thậm chí còn miễn cưỡng sử dụng các macro khi tôi đang xử lý trình biên dịch C không hỗ trợ các hàm nội tuyến. Và tôi cố gắng không sử dụng chúng để tạo ra các chức năng kiểu bất khả tri nếu có thể (tạo ra một số chức năng với một chỉ báo kiểu như là một phần của tên thay thế).

Tôi cũng đã chuyển sang sử dụng enums cho các hằng số số có tên thay vì #define.

+0

Một trường hợp hữu ích khác cho macro là để khởi tạo đối tượng trong phân đoạn dữ liệu: int global = radian2degree (2 * PI); – calandoa

+0

Yup - Tôi hiếm khi xử lý các số dấu phẩy động, vì vậy tôi thường sử dụng enums ngay thay vì các macro cho các hằng số được đặt tên. Nhưng rõ ràng là không thể thực hiện cho các phao nổi như 'PI', do đó, nó sẽ là một vĩ mô. –

+0

Trên các trình biên dịch với các phần mở rộng gcc, các macro, không giống như các hàm nội tuyến, có thể kiểm tra xem các đối số của chúng có thể được đánh giá là các hằng số hay không. Nếu người ta muốn có một "hàm" giống như 'reverse8bits (x)' tính toán giá trị byte đảo ngược bit, có thể tốt nhất là sử dụng '(((x & 128) >> 7) | ((x & 64) >> 5) ...) 'khi' x' là hằng số (trong trường hợp biểu thức trông khó chịu tạo ra một hằng số) nhưng gọi một thường trình thư viện khi 'x' là một biến. Tôi muốn 'C' cho phép các hàm nội tuyến bị quá tải dựa trên các đối số có phải là hằng số biên dịch hay không, vì nó sẽ sạch hơn, và ... – supercat

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