2009-10-24 41 views
11

Tôi đã đọc một số mã viết bằng C tối nay, và ở phía trên cùng của file là HASH vĩ mô chức năng giống như:Khi nào thì sử dụng macro chức năng giống như trong C

#define HASH(fp) (((unsigned long)fp)%NHASH) 

này để lại cho tôi tự hỏi , tại sao ai đó chọn để thực hiện một hàm theo cách này bằng cách sử dụng macro giống như hàm thay vì thực hiện nó dưới dạng hàm vani bình thường? Những lợi thế và những bất lợi của của từng triển khai là gì?

Cảm ơn một nhóm!

+0

Khi hàm băm đi, điều đó khá yếu - nhưng sau đó, có vẻ như dữ liệu phải xử lý cũng khá đơn giản. –

+0

Trên thực tế, nó không phải là rất tươi sáng để làm cho điều này một 'vĩ mô'. Một [hàm tĩnh nội tuyến] (http://stackoverflow.com/questions/7762731/whats-the-difference-between-static-and-static-inline-function) có lẽ phù hợp hơn. * C * đã có nội tuyến từ năm 1999 và 'gcc' ngay cả trước đó. Câu hỏi này đã được trích dẫn là nhân bản [sử dụng định nghĩa kfifo] (http://stackoverflow.com/questions/16117304/why-is-kfifo-h-so-full-of-define-statements). Nếu ** MS-Windows ** là lý do mọi người nói macro này là hợp lý, chúng có liên quan như thế nào? –

Trả lời

3

Một mặt, macro là xấu bởi vì chúng được thực hiện bởi bộ tiền xử lý, không hiểu bất cứ điều gì về ngôn ngữ và thay thế văn bản. Chúng thường có nhiều hạn chế. Tôi không thể nhìn thấy một ở trên, nhưng thường là các giải pháp xấu xí.

Mặt khác, chúng có lúc nhanh hơn phương pháp static inline. Tôi đã tối ưu hóa rất nhiều một chương trình ngắn và thấy rằng việc gọi phương thức static inline mất khoảng gấp đôi thời gian (chỉ trên không, không phải là chức năng thực tế của cơ thể) so với macro.

+0

Điều đó rất hữu ích. Cảm ơn bạn! Chỉ cần tò mò, bạn đã sử dụng công cụ nào để tối ưu hóa mã C của mình? Tôi chưa thử tối ưu hóa trước đây và có thể muốn thử. – Ralph

+0

Công cụ? Vim, 'gprof' và lệnh' time' đáng kính. 'gprof' là một profiler khá tốt với chi phí thấp, mặc dù bạn phải cẩn thận vì nó không theo dõi các macro (nó không nhìn thấy chúng; một nhược điểm khác của macro). – pavpanchekha

+0

Bạn đang sử dụng phiên bản trình biên dịch nào và bạn yêu cầu mức tối ưu nào từ nó? –

0

Có thể sử dụng C Preprocessor để tạo các hàm nội dòng. Trong ví dụ của bạn, mã sẽ xuất hiện để gọi hàm HASH, nhưng thay vào đó chỉ là mã nội tuyến.

Lợi ích của việc thực hiện các chức năng macro đã bị loại bỏ khi C++ giới thiệu các hàm nội dòng. Nhiều API cũ hơn như MFC và ATL vẫn sử dụng các hàm macro để thực hiện các thủ thuật tiền xử lý, nhưng nó chỉ để lại mã phức tạp và khó đọc hơn.

1

dụ bạn không phải là thực sự là một chức năng nào cả,

 
#define HASH(fp) (((unsigned long)fp)%NHASH) 
// this is a cast ^^^^^^^^^^^^^^^ 
// this is your value 'fp'  ^^ 
// this is a MOD operation   ^^^^^^ 

Tôi nghĩ rằng, đây chỉ là một cách viết code dễ đọc hơn với việc chọn diễn viên và mod opration quấn vào một macro đơn 'HASH(fp)'


Bây giờ, nếu bạn quyết định viết một hàm cho điều này, nó có thể sẽ như thế nào,

 
int hashThis(int fp) 
{ 
    return ((fp)%NHASH); 
} 

Khá một overkill cho một chức năng như nó,

  • giới thiệu một điểm gọi
  • giới thiệu gọi-stack cài đặt và khôi phục
+0

điểm rất tốt! – Ralph

24

Macros như vậy tránh được những chi phí của một cuộc gọi chức năng.

Có vẻ như không nhiều.Nhưng trong ví dụ của bạn, macro biến thành 1-2 lệnh ngôn ngữ máy, tùy thuộc vào CPU của bạn:

  • Lấy giá trị của fp ra khỏi bộ nhớ và đặt nó trong một thanh ghi
  • Lấy giá trị trong thanh ghi , làm một mô đun (%) tính toán bởi một giá trị cố định, và rời khỏi đó trong cùng đăng ký

trong khi chức năng tương đương sẽ là rất nhiều lệnh ngôn ngữ máy, nói chung là cái gì đó như

  • Stick giá trị của fp trên stack
  • Gọi chức năng, mà cũng đặt tiếp theo (cửa sổ mới) giải quyết trên stack
  • lẽ xây dựng một stack frame bên trong hàm, phụ thuộc vào kiến ​​trúc CPU và ước ABI
  • Lấy giá trị của fp ra khỏi stack và đặt nó trong một thanh ghi
  • Lấy giá trị trong thanh ghi, thực hiện mô đun (%) tính toán bởi một giá trị cố định, và rời khỏi đó trong cùng đăng ký
  • lẽ lấy giá trị từ thanh ghi và đặt nó trở lại trên ngăn xếp, tùy thuộc vào CPU và ABI
  • Nếu một khung đống bị xây dựng, hãy thư giãn nó
  • Pop địa chỉ trở lại ra khỏi ngăn xếp và tiếp tục hướng dẫn thực hiện có

đang Rất nhiều hơn, eh? Nếu bạn đang làm một cái gì đó giống như vẽ mỗi một trong hàng chục nghìn điểm ảnh trong một cửa sổ trong GUI, mọi thứ sẽ chạy nhanh hơn rất nhiều nếu bạn sử dụng macro.

Cá nhân, tôi thích sử dụng C++ nội tuyến dễ đọc hơn và ít bị lỗi hơn, nhưng nội tuyến cũng thực sự là một gợi ý cho trình biên dịch mà nó không phải mất. Các macro tiền xử lý là một búa búa mà trình biên dịch không thể tranh luận được.

+0

wow, bạn nói đúng, đó là cách mã nhiều hơn để thực hiện nó như là một chức năng! – Ralph

+5

Đó là những chức năng nội tuyến dành cho! :) – LiraNuna

+0

LiraNuna: chức năng nội tuyến không phải là một phần của ANSI C90, đó là những gì hầu hết các trình biên dịch hỗ trợ. Họ chỉ có trong C99, hoặc C++ –

3

Lý do phổ biến nhất (và thường xuyên sai) nhất mà mọi người đưa ra khi sử dụng macro (trong "C cũ") là đối số hiệu quả. Sử dụng chúng cho hiệu quả là tốt nếu bạn đã thực sự profiled mã của bạn và đang tối ưu hóa một nút cổ chai thực sự (hoặc đang viết một chức năng thư viện có thể là một nút cổ chai cho ai đó một ngày nào đó). Nhưng hầu hết những người nhấn mạnh vào việc sử dụng chúng đã không thực sự phân tích bất cứ điều gì và chỉ là tạo ra sự nhầm lẫn, nơi nó không có lợi ích.

Macro cũng có thể được sử dụng cho một số thay thế loại tìm kiếm và thay thế tiện dụng mà ngôn ngữ C thông thường không có khả năng.

Một số vấn đề mà tôi đã có trong việc duy trì mã được viết bởi kẻ lạm dụng macro là các macro có thể trông giống như các hàm nhưng không hiển thị trong bảng biểu tượng, vì vậy có thể rất khó chịu để theo dõi chúng trở lại nguồn gốc của chúng các tập mã sắc màu rực rỡ (điều này được xác định ở đâu ?!). Viết macro trong ALL CAPS rõ ràng là hữu ích cho người đọc trong tương lai.

Nếu chúng thay thế khá đơn giản, chúng cũng có thể tạo ra một số nhầm lẫn nếu bạn phải theo dõi qua chúng bằng trình gỡ lỗi.

+0

bạn có thể đưa ra ví dụ về một trong những thay thế tìm kiếm và thay thế này chỉ có thể được thực hiện với các macro mà bạn nói đến không? – Ralph

+3

Các dòng tạo mã như: #define COLORVAL (màu, val) int color ## _ number = val; Tôi sẽ không làm điều này bản thân mình quá nhiều, nhưng nếu bạn cần dòng cho màu đỏ, xanh dương, xanh lá cây, ..., đen, sau đó nó có thể giúp đỡ. – JXG

+0

tr.212 của "Sổ tay Hater của Unix" (tr.248 trong .pdf http://www.simson.net/ref/ugh.pdf, từ http://www.cs.washington.edu/homes/ weise/uhh-download.html) thảo luận về một số khó khăn liên quan. – Kilo

16

Một lợi thế quan trọng của việc triển khai dựa trên macro là nó không gắn với bất kỳ loại tham số cụ thể nào. Một macro giống như hàm trong các hành vi C, theo nhiều khía cạnh, như một hàm mẫu trong C++ (các mẫu trong C++ được sinh ra là các macro "văn minh hơn", BTW). Trong trường hợp cụ thể này, đối số của macro không có loại cụ thể.Nó có thể là hoàn toàn bất cứ điều gì có thể chuyển đổi thành loại unsigned long. Ví dụ, nếu người dùng rất hài lòng (và nếu họ sẵn sàng chấp nhận các hậu quả được thực hiện), họ có thể chuyển các kiểu con trỏ tới macro này.

Dù sao, tôi phải thừa nhận rằng macro này không phải là ví dụ tốt nhất về tính linh hoạt loại độc lập của macro, nhưng nói chung tính linh hoạt trở nên tiện dụng khá thường xuyên. Một lần nữa, khi chức năng nhất định được thực hiện bởi một hàm, nó được giới hạn trong các kiểu tham số cụ thể. Trong nhiều trường hợp để áp dụng hoạt động tương tự cho các kiểu khác nhau, cần cung cấp một số hàm với các kiểu tham số khác nhau (và các tên khác nhau, vì đây là C), trong khi cùng có thể được thực hiện chỉ bằng một macro giống hàm. Ví dụ, vĩ mô

#define ABS(x) ((x) >= 0 ? (x) : -(x)) 

làm việc với tất cả các loại số học, trong khi thực hiện chức năng dựa trên phải cung cấp khá một vài trong số họ (tôi ngụ ý tiêu chuẩn abs, labs, llabsfabs). (Và có, tôi nhận thức được những mối nguy hiểm được đề cập theo truyền thống của macro đó.)

Macro không hoàn hảo, nhưng phổ biến nhất về "macro giống như chức năng không còn cần thiết vì chức năng nội tuyến" chỉ đơn giản là vô nghĩa . Để thay thế hoàn toàn các macro giống như hàm C sẽ cần các mẫu chức năng (như trong C++) hoặc ít nhất là quá tải hàm (như trong C++ một lần nữa). Nếu không có các macro giống như hàm đó và sẽ vẫn là công cụ chính thống cực kỳ hữu dụng trong C.

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