为什么C宏不是类型安全的?

时间:2022-08-18 16:01:39

If have encountered this claim multiple times and can't figure out what it is supposed to mean. Since the resulting code is compiled using a regular C compiler it will end up being type checked just as much (or little) as any other code.

如果多次遇到这种说法,并且不知道它应该是什么意思。由于生成的代码是使用普通的C编译器编译的,所以它的类型检查将与其他代码一样多(或少)。

So why are macros not type safe? It seems to be one of the major reasons why they should be considered evil.

那么为什么宏不是类型安全的呢?这似乎是他们被认为是邪恶的主要原因之一。

7 个解决方案

#1


11  

Well they're not directly type-safe... I suppose in certain scenarios/usages you could argue they can be indirectly (i.e. resulting code) type-safe. But you could certainly create a macro intended for integers and pass it strings... the pre-processor handling the macros certainly doesn't care. The compiler may choke on it, depending on usage...

嗯,它们不是直接类型安全的…我认为在某些场景/用法中,您可能会认为它们是间接的(即结果代码)类型安全的。但你当然可以为整数创建一个宏并传递它。处理宏的预处理器当然不在乎。编译器可能会被它卡住,这取决于使用情况……

#2


36  

Consider the typical "max" macro, versus function:

考虑典型的“max”宏与函数:

#define MAX(a,b) a < b ? a : b
int max(int a, int b) {return a < b ? a : b;}

Here's what people mean when they say the macro is not type-safe in the way the function is:

当人们说宏的类型不安全时,他们的意思是:

If a caller of the function writes

如果函数的调用者写入

char *foo = max("abc","def");

the compiler will warn.

编译器会发出警告。

Whereas, if a caller of the macro writes:

然而,如果宏的调用者写:

char *foo = MAX("abc", "def");

the preprocessor will replace that with:

预处理器将用:

char *foo = "abc" < "def" ? "abc" : "def";

which will compile with no problems, but almost certainly not give the result you wanted.

它的编译没有问题,但几乎肯定不会给出您想要的结果。

Additionally of course the side effects are different, consider the function case:

另外,当然副作用是不同的,考虑一下功能案例:

int x = 1, y = 2;
int a = max(x++,y++); 

the max() function will operate on the original values of x and y and the post-increments will take effect after the function returns.

max()函数将对x和y的原始值进行操作,后增量将在函数返回后生效。

In the macro case:

在宏观情况下:

int x = 1, y = 2;
int b = MAX(x++,y++);

that second line is preprocessed to give:

第二行经过预处理,得到:

int b = x++ < y++ ? x++ : y++;

Again, no compiler warnings or errors but will not be the behaviour you expected.

同样,没有编译器警告或错误,但不会是您期望的行为。

#3


11  

Macros aren't type safe because they don't understand types.

宏不是类型安全的,因为它们不理解类型。

You can't tell a macro to only take integers. The preprocessor recognises a macro usage and it replaces one sequence of tokens (the macro with its arguments) with another set of tokens. This is a powerful facility if used correctly, but it's easy to use incorrectly.

你不能让宏只取整数。预处理器识别一个宏的使用,并将一个令牌序列(宏及其参数)替换为另一组令牌。如果使用正确,这是一个强大的功能,但是很容易使用不正确。

With a function you can define a function void f(int, int) and the compiler will flag if you try to use the return value of f or pass it strings.

使用函数可以定义一个函数void f(int, int),如果您试图使用f的返回值或传递它的字符串,那么编译器将标记。

With a macro - no chance. The only checks that get made are it is given the correct number of arguments. then it replaces the tokens appropriately and passes onto the compiler.

宏观上没有机会。得到的唯一检查是给出正确的参数数目。然后,它适当地替换标记并将其传递给编译器。

#define F(A, B)

will allow you to call F(1, 2), or F("A", 2) or F(1, (2, 3, 4)) or ...

将允许您调用F(1, 2)或F(“A”,2)或F(1,(2, 3, 4)或…

You might get an error from the compiler, or you might not, if something within the macro requires some sort of type safety. But that's not down to the preprocessor.

您可能会从编译器中得到错误,或者您可能不会,如果宏中某些东西需要某种类型的安全性。但这并不取决于预处理器。

You can get some very odd results when passing strings to macros that expect numbers, as the chances are you'll end up using string addresses as numbers without a squeak from the compiler.

当将字符串传递到期望数字的宏时,您可能会得到一些非常奇怪的结果,因为您最终可能会使用字符串地址作为数字,而不会受到编译器的干扰。

#4


4  

Since macros are handled by the preprocessor, and the preprocessor doesn't understand types, it will happily accept variables that are of the wrong type.

由于宏是由预处理器处理的,而且预处理器不理解类型,所以它很乐意接受类型错误的变量。

This is usually only a concern for function-like macros, and any type errors will often be caught by the compiler even if the preprocessor doesn't, but this isn't guaranteed.

这通常只与类函数宏有关,而且即使预处理器没有,编译器也经常会捕获任何类型错误,但这并不能保证。

An example

一个例子

In the Windows API, if you wanted to show a balloon tip on an edit control, you'd use Edit_ShowBalloonTip. Edit_ShowBalloonTip is defined as taking two parameters: the handle to the edit control and a pointer to an EDITBALLOONTIP structure. However, Edit_ShowBalloonTip(hwnd, peditballoontip); is actually a macro that evaluates to

在Windows API中,如果希望在编辑控件上显示气球提示,可以使用Edit_ShowBalloonTip。Edit_ShowBalloonTip定义为接受两个参数:编辑控件的句柄和一个指向EDITBALLOONTIP结构的指针。然而,Edit_ShowBalloonTip(hwnd,peditballoontip);实际上是一个计算结果的宏吗

SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)(peditballoontip));

Since configuring controls is generally done by sending messages to them, Edit_ShowBalloonTip has to do a typecast in its implementation, but since it's a macro rather than an inline function, it can't do any type checking in its peditballoontip parameter.

由于配置控件通常是通过向它们发送消息来完成的,Edit_ShowBalloonTip必须在实现中进行类型转换,但是由于它是一个宏而不是内联函数,所以它不能在peditballoontip参数中进行任何类型检查。

A digression

一个题外话

Interestingly enough, sometimes C++ inline functions are a bit too type-safe. Consider the standard C MAX macro

有趣的是,有时候c++内联函数有点太类型安全了。考虑标准的C MAX宏

#define MAX(a, b) ((a) > (b) ? (a) : (b))

and its C++ inline version

以及它的c++内联版本。

template<typename T>
inline T max(T a, T b) { return a > b ? a : b; }

MAX(1, 2u) will work as expected, but max(1, 2u) will not. (Since 1 and 2u are different types, max can't be instantiated on both of them.)

MAX(1,2u)将按预期工作,但MAX(1,2u)不会。(因为1和2u是不同的类型,所以不能在它们上实例化max。)

This isn't really an argument for using macros in most cases (they're still evil), but it's an interesting result of C and C++'s type safety.

在大多数情况下,这并不是使用宏的理由(它们仍然是邪恶的),但这是C和c++类型安全性的一个有趣结果。

#5


2  

There are situations where macros are even less type-safe than functions. E.g.

在某些情况下,宏甚至不如函数的类型安全。如。

void printlog(int iter, double obj)
{
    printf("%.3f at iteration %d\n", obj, iteration);
}

Calling this with the arguments reversed will cause truncation and erroneous results, but nothing dangerous. By contrast,

使用参数反转调用它将导致截断和错误的结果,但不存在任何危险。相比之下,

#define PRINTLOG(iter, obj) printf("%.3f at iteration %d\n", obj, iter)

causes undefined behavior. To be fair, GCC warns about the latter, but not about the former, but that's because it knows printf -- for other varargs functions, the results are potentially disastrous.

导致未定义行为。公平地说,GCC对后者提出警告,但不是对前者,而是因为它知道printf——对于其他varargs函数,结果可能是灾难性的。

#6


1  

When the macro runs, it just does a text match through your source files. This is before any compilation, so it is not aware of the datatypes of anything it changes.

当宏运行时,它只是通过源文件进行文本匹配。这是在任何编译之前,因此它不知道它所更改的任何数据类型。

#7


1  

Macros aren't type safe, because they were never meant to be type safe.

宏不是类型安全的,因为它们从来就不是类型安全的。

The compiler does the type checking after macros had been expanded.

编译器在宏被展开后进行类型检查。

Macros and there expansion are meant as a helper to the ("lazy") author (in the sense of writer/reader) of C source code. That's all.

宏和扩展是作为C源代码的(“懒惰”)作者(即作者/读者)的助手。这是所有。

#1


11  

Well they're not directly type-safe... I suppose in certain scenarios/usages you could argue they can be indirectly (i.e. resulting code) type-safe. But you could certainly create a macro intended for integers and pass it strings... the pre-processor handling the macros certainly doesn't care. The compiler may choke on it, depending on usage...

嗯,它们不是直接类型安全的…我认为在某些场景/用法中,您可能会认为它们是间接的(即结果代码)类型安全的。但你当然可以为整数创建一个宏并传递它。处理宏的预处理器当然不在乎。编译器可能会被它卡住,这取决于使用情况……

#2


36  

Consider the typical "max" macro, versus function:

考虑典型的“max”宏与函数:

#define MAX(a,b) a < b ? a : b
int max(int a, int b) {return a < b ? a : b;}

Here's what people mean when they say the macro is not type-safe in the way the function is:

当人们说宏的类型不安全时,他们的意思是:

If a caller of the function writes

如果函数的调用者写入

char *foo = max("abc","def");

the compiler will warn.

编译器会发出警告。

Whereas, if a caller of the macro writes:

然而,如果宏的调用者写:

char *foo = MAX("abc", "def");

the preprocessor will replace that with:

预处理器将用:

char *foo = "abc" < "def" ? "abc" : "def";

which will compile with no problems, but almost certainly not give the result you wanted.

它的编译没有问题,但几乎肯定不会给出您想要的结果。

Additionally of course the side effects are different, consider the function case:

另外,当然副作用是不同的,考虑一下功能案例:

int x = 1, y = 2;
int a = max(x++,y++); 

the max() function will operate on the original values of x and y and the post-increments will take effect after the function returns.

max()函数将对x和y的原始值进行操作,后增量将在函数返回后生效。

In the macro case:

在宏观情况下:

int x = 1, y = 2;
int b = MAX(x++,y++);

that second line is preprocessed to give:

第二行经过预处理,得到:

int b = x++ < y++ ? x++ : y++;

Again, no compiler warnings or errors but will not be the behaviour you expected.

同样,没有编译器警告或错误,但不会是您期望的行为。

#3


11  

Macros aren't type safe because they don't understand types.

宏不是类型安全的,因为它们不理解类型。

You can't tell a macro to only take integers. The preprocessor recognises a macro usage and it replaces one sequence of tokens (the macro with its arguments) with another set of tokens. This is a powerful facility if used correctly, but it's easy to use incorrectly.

你不能让宏只取整数。预处理器识别一个宏的使用,并将一个令牌序列(宏及其参数)替换为另一组令牌。如果使用正确,这是一个强大的功能,但是很容易使用不正确。

With a function you can define a function void f(int, int) and the compiler will flag if you try to use the return value of f or pass it strings.

使用函数可以定义一个函数void f(int, int),如果您试图使用f的返回值或传递它的字符串,那么编译器将标记。

With a macro - no chance. The only checks that get made are it is given the correct number of arguments. then it replaces the tokens appropriately and passes onto the compiler.

宏观上没有机会。得到的唯一检查是给出正确的参数数目。然后,它适当地替换标记并将其传递给编译器。

#define F(A, B)

will allow you to call F(1, 2), or F("A", 2) or F(1, (2, 3, 4)) or ...

将允许您调用F(1, 2)或F(“A”,2)或F(1,(2, 3, 4)或…

You might get an error from the compiler, or you might not, if something within the macro requires some sort of type safety. But that's not down to the preprocessor.

您可能会从编译器中得到错误,或者您可能不会,如果宏中某些东西需要某种类型的安全性。但这并不取决于预处理器。

You can get some very odd results when passing strings to macros that expect numbers, as the chances are you'll end up using string addresses as numbers without a squeak from the compiler.

当将字符串传递到期望数字的宏时,您可能会得到一些非常奇怪的结果,因为您最终可能会使用字符串地址作为数字,而不会受到编译器的干扰。

#4


4  

Since macros are handled by the preprocessor, and the preprocessor doesn't understand types, it will happily accept variables that are of the wrong type.

由于宏是由预处理器处理的,而且预处理器不理解类型,所以它很乐意接受类型错误的变量。

This is usually only a concern for function-like macros, and any type errors will often be caught by the compiler even if the preprocessor doesn't, but this isn't guaranteed.

这通常只与类函数宏有关,而且即使预处理器没有,编译器也经常会捕获任何类型错误,但这并不能保证。

An example

一个例子

In the Windows API, if you wanted to show a balloon tip on an edit control, you'd use Edit_ShowBalloonTip. Edit_ShowBalloonTip is defined as taking two parameters: the handle to the edit control and a pointer to an EDITBALLOONTIP structure. However, Edit_ShowBalloonTip(hwnd, peditballoontip); is actually a macro that evaluates to

在Windows API中,如果希望在编辑控件上显示气球提示,可以使用Edit_ShowBalloonTip。Edit_ShowBalloonTip定义为接受两个参数:编辑控件的句柄和一个指向EDITBALLOONTIP结构的指针。然而,Edit_ShowBalloonTip(hwnd,peditballoontip);实际上是一个计算结果的宏吗

SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)(peditballoontip));

Since configuring controls is generally done by sending messages to them, Edit_ShowBalloonTip has to do a typecast in its implementation, but since it's a macro rather than an inline function, it can't do any type checking in its peditballoontip parameter.

由于配置控件通常是通过向它们发送消息来完成的,Edit_ShowBalloonTip必须在实现中进行类型转换,但是由于它是一个宏而不是内联函数,所以它不能在peditballoontip参数中进行任何类型检查。

A digression

一个题外话

Interestingly enough, sometimes C++ inline functions are a bit too type-safe. Consider the standard C MAX macro

有趣的是,有时候c++内联函数有点太类型安全了。考虑标准的C MAX宏

#define MAX(a, b) ((a) > (b) ? (a) : (b))

and its C++ inline version

以及它的c++内联版本。

template<typename T>
inline T max(T a, T b) { return a > b ? a : b; }

MAX(1, 2u) will work as expected, but max(1, 2u) will not. (Since 1 and 2u are different types, max can't be instantiated on both of them.)

MAX(1,2u)将按预期工作,但MAX(1,2u)不会。(因为1和2u是不同的类型,所以不能在它们上实例化max。)

This isn't really an argument for using macros in most cases (they're still evil), but it's an interesting result of C and C++'s type safety.

在大多数情况下,这并不是使用宏的理由(它们仍然是邪恶的),但这是C和c++类型安全性的一个有趣结果。

#5


2  

There are situations where macros are even less type-safe than functions. E.g.

在某些情况下,宏甚至不如函数的类型安全。如。

void printlog(int iter, double obj)
{
    printf("%.3f at iteration %d\n", obj, iteration);
}

Calling this with the arguments reversed will cause truncation and erroneous results, but nothing dangerous. By contrast,

使用参数反转调用它将导致截断和错误的结果,但不存在任何危险。相比之下,

#define PRINTLOG(iter, obj) printf("%.3f at iteration %d\n", obj, iter)

causes undefined behavior. To be fair, GCC warns about the latter, but not about the former, but that's because it knows printf -- for other varargs functions, the results are potentially disastrous.

导致未定义行为。公平地说,GCC对后者提出警告,但不是对前者,而是因为它知道printf——对于其他varargs函数,结果可能是灾难性的。

#6


1  

When the macro runs, it just does a text match through your source files. This is before any compilation, so it is not aware of the datatypes of anything it changes.

当宏运行时,它只是通过源文件进行文本匹配。这是在任何编译之前,因此它不知道它所更改的任何数据类型。

#7


1  

Macros aren't type safe, because they were never meant to be type safe.

宏不是类型安全的,因为它们从来就不是类型安全的。

The compiler does the type checking after macros had been expanded.

编译器在宏被展开后进行类型检查。

Macros and there expansion are meant as a helper to the ("lazy") author (in the sense of writer/reader) of C source code. That's all.

宏和扩展是作为C源代码的(“懒惰”)作者(即作者/读者)的助手。这是所有。