Tránh các phần mở rộng trình biên dịch không chuẩn và triển khai nó dưới dạng macro hoàn toàn an toàn loại trong tiêu chuẩn thuần túy C (ISO 9899: 2011).
Dung dịch
#define
GENERIC_MAX
(
x
,
y
)
((
x
)
>
(
y
)
?
(
x
)
:
(
y
))
#define
ENSURE_int
(
i
)
_Generic
((
i
),
int
:
(
i
))
#define
ENSURE_float
(
f
)
_Generic
((
f
),
float
:
(
f
))
#define
MAX
(
type
,
x
,
y
)
\
(
type
)
GENERIC_MAX
(
ENSURE_
##type(x), ENSURE_##type(y))
Sử dụng
MAX
(
int
,
2
,
3
)
Giải trình
Macro MAX tạo một macro khác dựa trên type
tham số. Macro điều khiển này, nếu được triển khai cho loại đã cho, được sử dụng để kiểm tra cả hai tham số có đúng loại không. Nếu type
không được hỗ trợ, sẽ có lỗi trình biên dịch.
Nếu x hoặc y không đúng loại, sẽ có lỗi trình biên dịch trong các ENSURE_
macro. Nhiều macro như vậy có thể được thêm vào nếu nhiều loại được hỗ trợ. Tôi đã giả định rằng chỉ các loại số học (số nguyên, số float, con trỏ, v.v.) sẽ được sử dụng và không sử dụng cấu trúc hoặc mảng, v.v.
Nếu tất cả các loại đều đúng, macro GENERIC_MAX sẽ được gọi. Cần thêm dấu ngoặc đơn xung quanh mỗi tham số macro, như biện pháp phòng ngừa tiêu chuẩn thông thường khi viết macro C.
Sau đó, có các vấn đề thường gặp với các chương trình khuyến mãi ngầm trong C. Nhà ?:
điều hành cân bằng toán hạng thứ 2 và thứ 3 với nhau. Ví dụ, kết quả của GENERIC_MAX(my_char1, my_char2)
sẽ là một int
. Để ngăn macro thực hiện các quảng cáo loại nguy hiểm tiềm tàng như vậy, loại cuối cùng được sử dụng cho loại dự định đã được sử dụng.
Cơ sở lý luận
Chúng tôi muốn cả hai tham số cho macro là cùng một loại. Nếu một trong số chúng thuộc loại khác, macro không còn là loại an toàn nữa, bởi vì một toán tử thích ?:
sẽ mang lại các khuyến mãi kiểu ngầm. Và bởi vì nó, chúng ta cũng luôn cần đưa kết quả cuối cùng trở lại loại dự định như đã giải thích ở trên.
Một macro chỉ với một tham số có thể đã được viết theo cách đơn giản hơn nhiều. Nhưng với 2 hoặc nhiều tham số, cần phải bao gồm một tham số loại phụ. Bởi vì điều này thật không may là không thể:
// this won't work
#define
MAX
(
x
,
y
)
\
_Generic
((
x
),
\
int
:
GENERIC_MAX
(
x
,
ENSURE_int
(
y
))
\
float
:
GENERIC_MAX
(
x
,
ENSURE_float
(
y
))
\
)
Vấn đề là nếu macro ở trên được gọi là MAX(1, 2)
với hai int
, nó vẫn sẽ cố gắng mở rộng macro tất cả các kịch bản có thể có của _Generic
danh sách liên kết. Vì vậy, ENSURE_float
macro cũng sẽ được mở rộng, mặc dù nó không liên quan int
. Và vì macro cố ý chỉ chứa float
loại, mã sẽ không được biên dịch.
Để giải quyết điều này, tôi đã tạo tên macro trong giai đoạn tiền xử lý thay vào đó, với toán tử ##, để không có macro nào vô tình được mở rộng.
Ví dụ
#include
<stdio.h>
#define
GENERIC_MAX
(
x
,
y
)
((
x
)
>
(
y
)
?
(
x
)
:
(
y
))
#define
ENSURE_int
(
i
)
_Generic
((
i
),
int
:
(
i
))
#define
ENSURE_float
(
f
)
_Generic
((
f
),
float
:
(
f
))
#define
MAX
(
type
,
x
,
y
)
\
(
type
)
GENERIC_MAX
(
ENSURE_
##type(x), ENSURE_##type(y))
int
main
(
void
)
{
int
ia
=
1
,
ib
=
2
;
float
fa
=
3.0f
,
fb
=
4.0f
;
double
da
=
5.0
,
db
=
6.0
;
printf
(
"%d\n"
,
MAX
(
int
,
ia
,
ib
));
// ok
printf
(
"%f\n"
,
MAX
(
float
,
fa
,
fb
));
// ok
//printf("%d\n", MAX(int, ia, fa)); compiler error, one of the types is wrong
//printf("%f\n", MAX(float, fa, ib)); compiler error, one of the types is wrong
//printf("%f\n", MAX(double, fa, fb)); compiler error, the specified type is wrong
//printf("%f\n", MAX(float, da, db)); compiler error, one of the types is wrong
//printf("%d\n", MAX(unsigned int, ia, ib)); // wont get away with this either
//printf("%d\n", MAX(int32_t, ia, ib)); // wont get away with this either
return
0
;
}