Các ứng dụng của toán tử tiền xử lý ## và gotchas cần xem xét là gì?


88

Như đã đề cập trong nhiều câu hỏi trước đây của tôi, tôi đang làm việc thông qua K&R và hiện đang sử dụng bộ tiền xử lý. Một trong những điều thú vị hơn - điều mà tôi chưa từng biết trước đây từ bất kỳ nỗ lực học C nào trước đây - là ##toán tử tiền xử lý. Theo K&R:

Toán tử tiền xử lý ## cung cấp một cách để nối các đối số thực tế trong quá trình mở rộng macro. Nếu một tham số trong văn bản thay thế liền kề với a ##, thì tham số đó sẽ được thay thế bằng đối số thực, ##khoảng trắng và khoảng trắng xung quanh bị xóa và kết quả được quét lại. Ví dụ: macro paste nối hai đối số của nó:

#define paste(front, back) front ## back

vì vậy paste(name, 1)tạo mã thông báo name1.

Làm thế nào và tại sao ai đó sẽ sử dụng điều này trong thế giới thực? Những ví dụ thực tế về việc sử dụng nó là gì và có những vấn đề gì cần xem xét không?

Câu trả lời:


47

CrashRpt: Sử dụng ## để chuyển đổi chuỗi nhiều byte macro sang Unicode

Cách sử dụng thú vị trong CrashRpt (thư viện báo cáo sự cố) như sau:

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

Ở đây họ muốn sử dụng chuỗi hai byte thay vì chuỗi một byte-per-char. Điều này có vẻ như nó thực sự vô nghĩa, nhưng họ làm điều đó vì một lý do chính đáng.

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

Họ sử dụng nó với một macro khác trả về một chuỗi với ngày và giờ.

Đặt Lbên cạnh a __ DATE __sẽ gây ra lỗi biên dịch.


Windows: Sử dụng ## cho chuỗi Unicode chung hoặc chuỗi nhiều byte

Windows sử dụng một cái gì đó như sau:

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

_Tđược sử dụng ở mọi nơi trong mã


Các thư viện khác nhau, sử dụng cho các tên công cụ truy cập và sửa đổi sạch:

Tôi cũng đã thấy nó được sử dụng trong mã để xác định trình truy cập và công cụ sửa đổi:

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

Tương tự như vậy, bạn có thể sử dụng cùng một phương pháp này cho bất kỳ kiểu tạo tên thông minh nào khác.


Nhiều thư viện khác nhau, sử dụng nó để thực hiện một số khai báo biến cùng một lúc:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;

3
Vì bạn có thể nối các ký tự chuỗi tại thời điểm biên dịch, bạn có thể giảm biểu thức BuildDate thành std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); và hoàn toàn xây dựng toàn bộ chuỗi cùng một lúc.
user666412 15/02/16

49

Một điều cần lưu ý khi bạn đang sử dụng toán tử tiền xử lý mã thông báo-paste (' ##') hoặc stringizing (' #') là bạn phải sử dụng thêm một cấp độ hướng dẫn để chúng hoạt động bình thường trong mọi trường hợp.

Nếu bạn không làm điều này và các mục được chuyển đến toán tử dán mã thông báo là macro, bạn sẽ nhận được kết quả có thể không như bạn muốn:

#include <stdio.h>

#define STRINGIFY2( x) #x
#define STRINGIFY(x) STRINGIFY2(x)
#define PASTE2( a, b) a##b
#define PASTE( a, b) PASTE2( a, b)

#define BAD_PASTE(x,y) x##y
#define BAD_STRINGIFY(x) #x

#define SOME_MACRO function_name

int main() 
{
    printf( "buggy results:\n");
    printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));

    printf( "\n" "desired result:\n");
    printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
}

Đầu ra:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21

1
Để biết giải thích về hành vi của bộ tiền xử lý này, hãy xem stackoverflow.com/questions/8231966/…
Adam Davis

@MichaelBurr Tôi đã đọc câu trả lời của bạn và tôi có một chút nghi ngờ. Tại sao LINE này lại in số dòng?
HELP PLZ

3
@AbhimanyuAryan: Tôi không chắc đây có phải là điều bạn đang hỏi hay không, nhưng __LINE__là một tên macro đặc biệt được thay thế bởi bộ tiền xử lý bằng số dòng hiện tại của tệp nguồn.
Michael Burr

Sẽ thật tuyệt nếu các đặc tả ngôn ngữ có thể được trích dẫn / liên kết, như ở đây
Antonio

14

Đây là một mẹo mà tôi đã gặp phải khi nâng cấp lên phiên bản mới của trình biên dịch:

Việc sử dụng không cần thiết toán tử dán mã thông báo ( ##) là không di động và có thể tạo ra khoảng trắng, cảnh báo hoặc lỗi không mong muốn.

Khi kết quả của toán tử dán mã thông báo không phải là mã thông báo tiền xử lý hợp lệ, thì toán tử dán mã thông báo là không cần thiết và có thể có hại.

Ví dụ: một người có thể cố gắng tạo các ký tự chuỗi tại thời điểm biên dịch bằng cách sử dụng toán tử dán mã thông báo:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a##+##b)
#define NS(a, b) STRINGIFY(a##::##b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

Trên một số trình biên dịch, điều này sẽ xuất ra kết quả mong đợi:

1+2 std::vector

Trên các trình biên dịch khác, điều này sẽ bao gồm khoảng trắng không mong muốn:

1 + 2 std :: vector

Các phiên bản GCC khá hiện đại (> = 3.3 hoặc lâu hơn) sẽ không biên dịch được mã này:

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token

Giải pháp là bỏ qua toán tử dán mã khi nối mã thông báo tiền xử lý với toán tử C / C ++:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a+b)
#define NS(a, b) STRINGIFY(a::b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

Chương tài liệu GCC CPP về ghép nối có nhiều thông tin hữu ích hơn về toán tử dán mã thông báo.


Cảm ơn - Tôi đã không biết về điều này (nhưng sau đó tôi không sử dụng các toán tử tiền xử lý này quá nhiều ...).
Michael Burr

3
Nó được gọi là toán tử "dán mã thông báo" vì một lý do - mục đích là kết thúc với một mã thông báo duy nhất khi bạn hoàn thành. Viết tốt lắm.
Mark Ransom vào

Khi kết quả của toán tử dán mã thông báo không phải là mã thông báo tiền xử lý hợp lệ, hành vi không được xác định.
alecov

Các thay đổi ngôn ngữ như dấu nổi hệ thập lục phân hoặc dấu phân tách chữ số (trong C ++) và các ký tự do người dùng xác định, liên tục thay đổi những gì cấu thành "mã thông báo tiền xử lý hợp lệ", vì vậy đừng bao giờ lạm dụng nó như vậy! Nếu bạn phải tách các mã thông báo (ngôn ngữ phù hợp), vui lòng đánh vần chúng thành hai mã thông báo riêng biệt và không dựa vào các tương tác ngẫu nhiên giữa ngữ pháp của bộ xử lý trước và ngôn ngữ thích hợp.
Kerrek SB

6

Điều này rất hữu ích trong mọi tình huống để không lặp lại chính mình một cách bất cần. Sau đây là một ví dụ từ mã nguồn Emacs. Chúng tôi muốn tải một số hàm từ thư viện. Hàm "foo" nên được gán cho fn_foo, v.v. Chúng tôi xác định macro sau:

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

Sau đó chúng ta có thể sử dụng nó:

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

Lợi ích là không phải viết cả fn_XpmFreeAttributes"XpmFreeAttributes"(và có nguy cơ viết sai một trong số chúng).


4

Một câu hỏi trước đây trên Stack Overflow đã yêu cầu một phương pháp trơn tru để tạo các biểu diễn chuỗi cho các hằng số liệt kê mà không cần phải nhập lại nhiều lỗi.

Liên kết

Câu trả lời của tôi cho câu hỏi đó đã cho thấy cách áp dụng phép thuật tiền xử lý nhỏ cho phép bạn xác định kiểu liệt kê của mình như thế này (ví dụ) ...;

ENUM_BEGIN( Color )
  ENUM(RED),
  ENUM(GREEN),
  ENUM(BLUE)
ENUM_END( Color )

... Với lợi ích mà việc mở rộng macro không chỉ xác định kiểu liệt kê (trong tệp .h), nó còn xác định một mảng các chuỗi phù hợp (trong tệp .c);

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

Tên của bảng chuỗi xuất phát từ việc dán tham số macro (tức là Màu) vào StringTable bằng cách sử dụng toán tử ##. Các ứng dụng (thủ thuật?) Như thế này là nơi mà các toán tử # và ## là vô giá.


3

Bạn có thể sử dụng cách dán mã thông báo khi cần nối các thông số macro với thứ gì đó khác.

Nó có thể được sử dụng cho các mẫu:

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

Trong trường hợp này LINKED_LIST (int) sẽ cung cấp cho bạn

struct list_int {
int value;
struct list_int *next;
};

Tương tự, bạn có thể viết một mẫu hàm để duyệt danh sách.


2

Tôi sử dụng nó trong các chương trình C để giúp thực thi chính xác các nguyên mẫu cho một tập hợp các phương thức phải tuân theo một số loại quy ước gọi. Theo một cách nào đó, điều này có thể được sử dụng cho hướng đối tượng của người nghèo trong đường thẳng C:

SCREEN_HANDLER( activeCall )

mở rộng thành một cái gì đó như thế này:

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

Điều này thực thi tham số chính xác cho tất cả các đối tượng "có nguồn gốc" khi bạn thực hiện:

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

ở trên trong các tệp tiêu đề của bạn, v.v. Nó cũng hữu ích cho việc bảo trì nếu bạn thậm chí muốn thay đổi định nghĩa và / hoặc thêm phương thức vào "đối tượng".


2

SGlib sử dụng ## để tạo khuôn mẫu về cơ bản trong C. Bởi vì không có hàm quá tải, ## được sử dụng để gắn tên kiểu vào tên của các hàm được tạo. Nếu tôi có một loại danh sách được gọi là list_t, thì tôi sẽ nhận được các hàm có tên như sglib_list_t_concat, v.v.


2

Tôi sử dụng nó cho một xác nhận được cuộn tại nhà trên trình biên dịch C không chuẩn để nhúng:



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 



3
Tôi hiểu ý bạn là 'không chuẩn' rằng trình biên dịch không thực hiện dán chuỗi nhưng đã dán mã thông báo - hoặc nó sẽ hoạt động ngay cả khi không có ##?
PJTraill

1

Tôi sử dụng nó để thêm tiền tố tùy chỉnh vào các biến được xác định bởi macro. Vì vậy, một cái gì đó như:

UNITTEST(test_name)

mở rộng thành:

void __testframework_test_name ()

1

Việc sử dụng chính là khi bạn có quy ước đặt tên và bạn muốn macro của mình tận dụng quy ước đặt tên đó. Có lẽ bạn có một số họ phương thức: image_create (), image_activate () và image_release () cũng như file_create (), file_activate (), file_release () và mobile_create (), mobile_activate () và mobile_release ().

Bạn có thể viết một macro để xử lý vòng đời của đối tượng:

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

Tất nhiên, một loại "phiên bản tối thiểu của các đối tượng" không phải là loại quy ước đặt tên duy nhất mà điều này áp dụng - gần như đại đa số các quy ước đặt tên sử dụng một chuỗi con chung để tạo tên. Nó có thể cho tôi tên hàm (như trên), hoặc tên trường, tên biến hoặc hầu hết bất kỳ thứ gì khác.


1

Một công dụng quan trọng trong WinCE:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

Trong khi xác định mô tả bit thanh ghi, chúng tôi làm như sau:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

Và trong khi sử dụng BITFMASK, chỉ cần sử dụng:

BITFMASK(ADDR)

0

Nó rất hữu ích cho việc ghi nhật ký. Bạn có thể làm:

#define LOG(msg) log_msg(__function__, ## msg)

Hoặc, nếu trình biên dịch của bạn không hỗ trợ chức năngfunc :

#define LOG(msg) log_msg(__file__, __line__, ## msg)

"Chức năng" ở trên ghi lại thông báo và hiển thị chính xác chức năng nào đã ghi lại một thông báo.

Cú pháp C ++ của tôi có thể không đúng.


1
Bạn đã cố gắng làm gì với điều đó? Nó sẽ hoạt động tốt nếu không có "##", vì không cần phải dán mã thông báo "," thành "msg". Bạn có đang cố gắng xâu chuỗi tin nhắn không? Ngoài ra, FILELINE phải ở dạng chữ hoa, không phải chữ thường.
bk1e 19/10/08

Quả thực là bạn đúng. Tôi cần tìm tập lệnh gốc để xem cách sử dụng ##. Xấu hổ cho tôi, không có cookie ngày hôm nay!
ya23 12/12/08
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.