Làm thế nào để nối hai lần với bộ tiền xử lý C và mở rộng một macro như trong đối số # # _ ## MACRO tinh?


152

Tôi đang cố gắng viết một chương trình trong đó tên của một số hàm phụ thuộc vào giá trị của một biến macro nhất định có macro như thế này:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Thật không may, vĩ mô NAME()biến điều đó thành

int some_function_VARIABLE(int a);

thay vì

int some_function_3(int a);

Vì vậy, đây rõ ràng là cách sai để đi về nó. May mắn thay, số lượng các giá trị khác nhau có thể có cho VARIABLE là nhỏ nên tôi chỉ có thể thực hiện #if VARIABLE == nvà liệt kê tất cả các trường hợp một cách riêng biệt, nhưng tôi đã tự hỏi liệu có một cách thông minh để làm điều đó.


3
Bạn có chắc chắn không muốn sử dụng con trỏ hàm thay thế?
Gyorgy Andottok

8
@Jurily - Con trỏ hàm làm việc trong thời gian chạy, bộ tiền xử lý hoạt động tại (trước) thời gian biên dịch. Có một sự khác biệt, ngay cả khi cả hai có thể được sử dụng cho cùng một nhiệm vụ.
Chris Lutz

1
Vấn đề là những gì nó được sử dụng là một thư viện hình học tính toán nhanh .. được thiết kế cho một chiều nhất định. Tuy nhiên, đôi khi ai đó muốn có thể sử dụng nó với một vài kích thước khác nhau (giả sử, 2 và 3) và do đó, người ta sẽ cần một cách dễ dàng để tạo mã với chức năng phụ thuộc vào thứ nguyên và tên loại. Ngoài ra, mã được viết bằng ANSI C nên công cụ C ++ thú vị với các mẫu và chuyên môn hóa không được áp dụng ở đây.
JJ.

2
Bỏ phiếu để mở lại vì câu hỏi này là cụ thể về mở rộng macro đệ quy và stackoverflow.com/questions/216875/USE-in-macros là một từ chung chung "nó tốt cho cái gì". Tiêu đề của câu hỏi này nên được thực hiện chính xác hơn.
Ciro Santilli 郝海东 冠状 病 事件 法轮功

Tôi muốn ví dụ này đã được giảm thiểu: điều tương tự xảy ra #define A 0 \n #define M a ## A: có hai cái ##không phải là chìa khóa.
Ciro Santilli 郝海东 冠状 病 事件

Câu trả lời:


223

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 xtheo 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,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_ 3tê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.


1
Đúng, điều này giải quyết vấn đề. Tôi biết mẹo với hai cấp độ đệ quy - tôi đã phải chơi với chuỗi số hóa ít nhất một lần - nhưng không biết làm thế nào để thực hiện điều này.
JJ.

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?
Robert Rüger

1
@ RobertRüger: nó nhân đôi độ dài của câu trả lời, nhưng tôi đã thêm thông tin để trình bày cpp -traditional. Lưu ý rằng không có câu trả lời dứt khoát - nó phụ thuộc vào bộ tiền xử lý mà bạn có.
Jonathan Leffler

Cảm ơn bạn rất nhiều cho câu trả lời. Điều này là hoàn toàn tuyệt vời! Trong khi đó tôi cũng tìm thấy một giải pháp khác, hơi khác. Xem ở đây . Nó cũng có vấn đề là nó không hoạt động với tiếng kêu. May mắn thay, đó không phải là vấn đề đối với ứng dụng của tôi ...
Robert Rüger

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Thành thật mà nói, bạn không muốn biết tại sao điều này hoạt động. Nếu bạn biết lý do tại sao nó hoạt động, bạn sẽ trở thành anh chàng đó tại nơi làm việc biết loại này và mọi người sẽ đến hỏi bạn câu hỏi. =)

Chỉnh sửa: nếu bạn thực sự muốn biết lý do tại sao nó hoạt động, tôi sẽ vui vẻ đăng một lời giải thích, cho rằng không ai đánh bại tôi với nó.


Bạn có thể giải thích lý do tại sao nó cần hai cấp độ gián tiếp. Tôi đã có câu trả lời với một cấp độ chuyển hướng nhưng tôi đã xóa câu trả lời vì tôi phải cài đặt C ++ vào Visual Studio của mình và sau đó nó sẽ không hoạt động.
Cade Roux
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.