Bộ xử lý chuẩn C
$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)
extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"
extern void mine_3(char *x);
$
Hai cấp độ gián tiếp
Trong một bình luận cho một câu trả lời khác, Cade Roux đã hỏi tại sao điều này cần hai mức độ gián tiếp. Câu trả lời ngắn gọn là bởi vì đó là cách tiêu chuẩn yêu cầu nó hoạt động; bạn có xu hướng tìm thấy bạn cũng cần thủ thuật tương đương với toán tử xâu chuỗi.
Mục 6.10.3 của tiêu chuẩn C99 bao gồm 'thay thế macro' và 6.10.3.1 bao gồm 'thay thế đối số'.
Sau khi các đối số cho việc gọi macro giống như hàm đã được xác định, việc thay thế đối số diễn ra. Một tham số trong danh sách thay thế, trừ khi có trước #
hoặc ##
mã thông báo tiền xử lý hoặc theo sau là ##
mã thông báo tiền xử lý (xem bên dưới), được thay thế bằng đối số tương ứng sau khi tất cả các macro có trong đó được mở rộng. Trước khi được thay thế, các mã thông báo tiền xử lý của mỗi đối số được thay thế hoàn toàn macro như thể chúng tạo thành phần còn lại của tệp tiền xử lý; không có mã thông báo tiền xử lý khác có sẵn.
Trong lời gọi NAME(mine)
, đối số là 'của tôi'; nó được mở rộng hoàn toàn thành 'của tôi'; sau đó nó được thay thế vào chuỗi thay thế:
EVALUATOR(mine, VARIABLE)
Bây giờ, EVALUATOR vĩ mô được phát hiện và các đối số được phân lập là 'của tôi' và 'BIẾN'; cái sau đó được mở rộng hoàn toàn thành '3' và được thay thế thành chuỗi thay thế:
PASTER(mine, 3)
Hoạt động của điều này được bao phủ bởi các quy tắc khác (6.10.3.3 'Toán tử ##'):
Nếu, trong danh sách thay thế của macro giống như hàm, một tham số ngay lập tức được đi trước hoặc theo sau bởi ##
mã thông báo tiền xử lý, tham số được thay thế bằng chuỗi mã thông báo tiền xử lý của đối số tương ứng; [...]
Đối với cả các yêu cầu macro giống như đối tượng và chức năng, trước khi danh sách thay thế được xem xét lại để thay thế nhiều tên macro hơn, mỗi phiên bản của một ##
mã thông báo tiền xử lý trong danh sách thay thế (không phải từ một đối số) sẽ bị xóa và mã thông báo tiền xử lý trước được nối với mã thông báo tiền xử lý sau.
Vì vậy, danh sách thay thế chứa x
theo sau ##
và cũng ##
theo sau y
; vì vậy chúng tôi có:
mine ## _ ## 3
và loại bỏ các ##
mã thông báo và ghép các mã thông báo ở hai bên kết hợp 'của tôi' với '_' và '3' để mang lại:
mine_3
Đây là kết quả mong muốn.
Nếu chúng ta xem xét câu hỏi ban đầu, mã đã được (điều chỉnh để sử dụng 'của tôi' thay vì 'some_feft'):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
Đối số với NAME rõ ràng là 'của tôi' và điều đó được mở rộng hoàn toàn.
Theo các quy tắc của 6.10.3.3, chúng tôi tìm thấy:
mine ## _ ## VARIABLE
trong đó, khi các ##
toán tử được loại bỏ, ánh xạ tới:
mine_VARIABLE
chính xác như báo cáo trong câu hỏi.
Tiền xử lý C truyền thống
Robert Rüger hỏi :
Có cách nào để làm điều này với bộ tiền xử lý C truyền thống không có toán tử dán mã thông báo ##
không?
Có thể, và có thể không - nó phụ thuộc vào bộ tiền xử lý. Một trong những lợi thế của bộ tiền xử lý tiêu chuẩn là nó có cơ sở này hoạt động đáng tin cậy, trong khi đó có các cách triển khai khác nhau cho tiền xử lý chuẩn. Một yêu cầu là khi bộ tiền xử lý thay thế một nhận xét, nó không tạo ra một khoảng trắng vì bộ tiền xử lý ANSI là bắt buộc phải làm. Bộ tiền xử lý GCC (6.3.0) C đáp ứng yêu cầu này; bộ tiền xử lý Clang từ XCode 8.2.1 thì không.
Khi nó hoạt động, điều này thực hiện công việc ( x-paste.c
):
#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
Lưu ý rằng không có khoảng cách giữa fun,
và VARIABLE
- điều đó rất quan trọng bởi vì nếu có, nó được sao chép vào đầu ra và cuối cùng bạn có mine_ 3
tên là không hợp lệ về mặt cú pháp. (Bây giờ, xin vui lòng tôi có thể lấy lại tóc của tôi?)
Với GCC 6.3.0 (đang chạy cpp -traditional x-paste.c
), tôi nhận được:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_3(char *x);
Với Clang từ XCode 8.2.1, tôi nhận được:
# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2
extern void mine _ 3(char *x);
Những không gian đó làm hỏng mọi thứ. Tôi lưu ý rằng cả hai tiền xử lý đều đúng; các bộ tiền xử lý tiêu chuẩn khác nhau thể hiện cả hai hành vi, điều này làm cho mã thông báo dán một quy trình cực kỳ khó chịu và không đáng tin cậy khi cố gắng mã hóa cổng. Các tiêu chuẩn với ##
ký hiệu hoàn toàn đơn giản hóa điều đó.
Có thể có những cách khác để làm điều này. Tuy nhiên, điều này không hoạt động:
#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
GCC tạo ra:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
Đóng, nhưng không có xúc xắc. YMMV, tất nhiên, tùy thuộc vào bộ tiền xử lý tiêu chuẩn mà bạn đang sử dụng. Thành thật mà nói, nếu bạn bị mắc kẹt với bộ tiền xử lý không hợp tác, có lẽ sẽ đơn giản hơn khi sắp xếp sử dụng bộ tiền xử lý C tiêu chuẩn thay cho bộ tiền xử lý chuẩn (thường có cách định cấu hình trình biên dịch phù hợp) so với dành nhiều thời gian cố gắng tìm ra cách để thực hiện công việc.