一、一个最简单的C程序
#include <stdio.h>
int main(void)
{
int num = num;
return 0;
}
这个程序在GCC上编译,即便时加上-Wall编译参数,也不会报任何编译警告或错误。但这个程序在Visual Studio 2019上进行编译的时候,却会有一个错误,如下图所示:
程序很简单,所以一眼就能看出出问题的代码就是int num = num;
这一行。依据编译错误信息,我们可以判断出问题其实就在赋值符号’='右边的num
。这说明,编译器是先声明变量,然后把这个变量未经初始化的原始的值(此值和程序之前运行的状态有关)赋值给了自己。
二、再看一个C程序
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
int num1 = 0x11223344;
int num2 = 0x55667788;
printf("Outer: num1 = %#x, num2 = %#x\n", num1, num2);
{
int a = num1;
int b = num2;
printf("Inner: a = %#x, b = %#x\n", num1, num2);
}
return EXIT_SUCCESS;
}
这个程序相比前面一个程序,其结构确实复杂了一点,但我们还是能分析出其计算结果。
三、再来一个C程序
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
int num1 = 0x11223344;
int num2 = 0x55667788;
printf("Outer: num1 = %#x, num2 = %#x\n", num1, num2);
{
int num1 = (num1);
int num2 = (num2);
printf("Inner: num1 = %#x, num2 = %#x\n", num1, num2);
}
return EXIT_SUCCESS;
}
写这个程序的初衷是把外层的变量num1和num2赋值给内层的变量num1和num2。但是得到的结果却和我们的初衷有很大的不同。在GCC上编译无错误,但运行结果却如下图所示:
在Visual Studio 2019上编译有如下错误信息:
再结合C语言的变量可见性规则(PS:内层语句块声明的变量,会屏蔽外层语句块声明的同名变量),我们可以很快明白错误的问题所在:因为内外层语句块的变量的值是不可能赋值给内层语句块的同名变量。
通常情况下,我们应该很难犯这种错误,很可能是因为我们觉得同名变量会扰乱我们的思维逻辑。
四、最后来一个C程序
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define FUN(a, b) \
{ \
int num1 = (a); \
int num2 = (b); \
printf("Inner: num1 = %#x, num2 = %#x\n", num1, num2); \
}
int main(void)
{
int num1 = 0x11223344;
int num2 = 0x55667788;
printf("Outer: num1 = %#x, num2 = %#x\n", num1, num2);
FUN(num1, num2);
return EXIT_SUCCESS;
}
这个程序的意图和上一个程序是一样的,只不过使用了宏函数。不论是GCC还是Visual Studio 2019,编译或执行结果都是与前一个程序是一样的。单看宏函数FUN()确实是没问题的。但如果把这个宏函数展开后,我们就会发现,问题的原因和前一个程序存在的问题是一样的:外层语句块的变量被内层语句块的同名变量屏蔽了。
“函数本身是一个以接口为分界面,屏蔽函数内外具体实现细节,来实现具体功能的代码块。”这句话中包含的一个重要特征是,主调函数的具体实现不能影响被调函数内部的运行;反过来也是一样,被调函数的具体实现不能影响主调函数的运行。但是,到此我们发现了一个很严重的问题:宏函数中,如果在其内部声明了一个与主调函数同名的变量,同时这个变量以形参的形式传递给宏函数,那么程序可能会出错。这个问题的根本原因是:宏函数本质上不是一个真正的函数,它只是一个简单的文本替换。
五、正确的实现方式
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
static inline void FUNC_RIGHT(int a, int b)
{
int num1 = (a);
int num2 = (b);
printf("Inner: num1 = %#x, num2 = %#x\n", num1, num2);
}
int main(void)
{
//int num = num;
int num1 = 0x11223344;
int num2 = 0x55667788;
printf("Outer: num1 = %#x, num2 = %#x\n", num1, num2);
FUNC_RIGHT(num1, num2);
return EXIT_SUCCESS;
}
在GCC上,static inline会将其修饰的函数设定成内联函数。笔者不是很确定static inline在Visual Studio 2019上的编译效果,但是即便和GCC不一样,这个函数也可以编译成一个“正常”的函数,应该不存在问题。