在微控制器上,为了避免加载之前固件版本的设置,我还存储编译时间,在加载时检查该时间。
该微控制器项目是使用MikroElektronika 的“ mikroC PRO for ARM”构建的。
为了方便调试,我在PC上用minGW编写了代码,左右检查后,将其放入microC中。
使用该检查的代码无法正常工作。经过一晚令人沮丧的调试后,我发现sizeof("...")
在两个平台上产生了不同的值,并因此导致缓冲区溢出。
但现在我不知道是谁的错。
要重新创建问题,请使用以下代码:
#define SAVEFILECHECK_COMPILE_DATE __DATE__ " " __TIME__
char strA[sizeof(SAVEFILECHECK_COMPILE_DATE)];
char strB[] = SAVEFILECHECK_COMPILE_DATE;
printf("sizeof(#def): %d\n", (int)sizeof(SAVEFILECHECK_COMPILE_DATE));
printf("sizeof(strA): %d\n", (int)sizeof(strA));
printf("sizeof(strB): %d\n", (int)sizeof(strB));
在 MinGW 上它返回(如预期):
sizeof(#def): 21
sizeof(strA): 21
sizeof(strB): 21
但是,在“mikroC PRO for ARM”上,它返回:
sizeof(#def): 20
sizeof(strA): 20
sizeof(strB): 21
这种差异导致了缓冲区溢出(覆盖指针的字节零——哎呀)。
21 是我期望的答案:20 个字符和 '\0' 终止符。
这是 C 中“视情况而定”的事情之一,还是违反了sizeof
运算符行为?
这一切都是100%标准化的。C17 6.10.8.1:
" "
(用于字符串文字连接的空格)= 111 + 8 + 1 + 1 = 21
至于
sizeof
,字符串文字是一个数组。每当您将声明的数组传递给 时sizeof
,该数组都不会“衰减”为指向第一个元素的指针,因此sizeof
将报告数组的大小(以字节为单位)。对于字符串文字,这包括 null 终止符,C17 6.4.5:(还提到了翻译阶段 6,即字符串文字连接阶段。即字符串文字连接保证在添加空终止之前发生。)
因此,mikroC PRO 似乎不合格/存在缺陷。肯定有很多有问题的嵌入式系统编译器。
C 标准中完整定义了该行为。以下是 C99 已发布标准的相关引用,除了 C90 (ANSI C) 版本中的章节编号外,这些标准是相同的,并且在更新版本(包括即将推出的 C23 版本)中没有进行本质上的修改:
和宏由
__DATE__
以下__TIME__
指定从上面可知,如果翻译时间可用,宏
SAVEFILECHECK_COMPILE_DATE
将扩展为 3 个字符串文字,总共 11+1+8 = 20 个字符,因此包括空终止符在内有 21 个字节。如果翻译时间不可用,则必须使用实现定义的有效日期和时间,因此行为必须相同。sizeof
因此,由 3 个相邻字符串文字组成的参数是无关紧要的,sizeof
示例中所有出现的运算符都会在第 7 阶段获得单个字符串文字参数,然后因此,示例中的所有 3 个输出都必须显示 21 个字节。您在mikroc编译器中发现了一个错误:您应该报告它并为您当前的项目找到解决方法。
这是一个编译器错误。字符串文字,无论它们是由单个带引号的序列还是由多个相邻的带引号的序列组成,都存储为始终包含终止空字节的静态数组。这种事本该发生的地方却没有发生。
C 标准第 6.4.5p6 节中有关字符串文字的内容对此进行了规定:
这意味着
sizeof(SAVEFILECHECK_COMPILE_DATE)
应该计算字符串中的字符和终止空字节,但编译器由于某种原因不包括空字节。sizeof
正如其他人所指出的,字符串文字上的行为早已被标准化为产生一个比由此表示的字符串长度大一的值,而不是可以使用该字符串文字初始化的最小字符数组的大小。话虽如此,如果希望使代码即使与采用后一种解释的编译器兼容,我建议使用类似的表达式,(1-(sizeof "")+(sizeof "stringLiteral of interst"))
这样可以允许代码与古怪的编译器正确运行,但避免牺牲与标准编译器的兼容性。有趣的是,在这种情况下,
"aa"
不会衰减为指针,而是充当 char 数组。由于数组有 3 个元素(包括零终止符),因此输出为 3。这定义了字符串(字符数组)
每次编译它都是不同的,因为
__DATE__
和__TIME__
。我目前的结果是 21,但可能会改变。
对于 C++ 也同样有效。