sizeof("string") 的正确输出是什么?

cFs*_*chb 39 c sizeof string-literals language-lawyer mikroc

在微控制器上,为了避免加载之前固件版本的设置,我还存储编译时间,在加载时检查该时间。

\n

该微控制器项目是使用MikroElektronika 的mikroC PRO for ARM”构建的。

\n

为了方便调试,我在PC上用minGW编写了代码,左右检查后,将其放入microC中。

\n

使用该检查的代码无法正常工作。经过一晚令人沮丧的调试后,我发现sizeof("...")在两个平台上产生了不同的值,并因此导致缓冲区溢出。

\n

但现在我不知道这是谁的错。

\n

要重新创建问题,请使用以下代码:

\n
#define SAVEFILECHECK_COMPILE_DATE __DATE__ " " __TIME__\n\nchar strA[sizeof(SAVEFILECHECK_COMPILE_DATE)];\nchar strB[] = SAVEFILECHECK_COMPILE_DATE;\n\nprintf("sizeof(#def): %d\\n", (int)sizeof(SAVEFILECHECK_COMPILE_DATE));\nprintf("sizeof(strA): %d\\n", (int)sizeof(strA));\nprintf("sizeof(strB): %d\\n", (int)sizeof(strB));\n
Run Code Online (Sandbox Code Playgroud)\n

在 MinGW 上它返回(如预期):

\n
#define SAVEFILECHECK_COMPILE_DATE __DATE__ " " __TIME__\n\nchar strA[sizeof(SAVEFILECHECK_COMPILE_DATE)];\nchar strB[] = SAVEFILECHECK_COMPILE_DATE;\n\nprintf("sizeof(#def): %d\\n", (int)sizeof(SAVEFILECHECK_COMPILE_DATE));\nprintf("sizeof(strA): %d\\n", (int)sizeof(strA));\nprintf("sizeof(strB): %d\\n", (int)sizeof(strB));\n
Run Code Online (Sandbox Code Playgroud)\n

然而,在“mikroC PRO for ARM”上,它返回:

\n
sizeof(#def): 21\nsizeof(strA): 21\nsizeof(strB): 21\n
Run Code Online (Sandbox Code Playgroud)\n

这种差异导致了缓冲区溢出(覆盖了指针 \xe2\x80\x93 的字节零)。

\n

21 是我期望的答案:20 个字符和 \'\\0\' 终止符。

\n

这是 C 中“视情况而定”的事情之一,还是违反了sizeof运算符行为?

\n

Lun*_*din 42

这一切都是100%标准化的。C17 6.10.8.1:

__DATE__ 预处理翻译单元的翻译日期:...形式的字符串文字,如果值小于 10,则"Mmm dd yyyy"第一个字符是空格字符。 ... 预处理翻译单元的翻译时间:形式的字符串文字dd

__TIME__"hh:mm:ss"

  • “嗯 dd 年” = 11
  • “时:分:秒”= 8
  • " "(用于字符串文字连接的空格)= 1
  • 空终止 = 1

11 + 8 + 1 + 1 = 21

至于sizeof,字符串文字是一个数组。每当您将声明的数组传递给 时sizeof,该数组都不会“衰减”为指向第一个元素的指针,因此sizeof将报告数组的大小(以字节为单位)。对于字符串文字,这包括 null 终止符,C17 6.4.5:

在翻译阶段 7,值为零的字节或代码被附加到由一个或多个字符串文字产生的每个多字节字符序列。然后,使用多字节字符序列来初始化静态存储持续时间和长度足以包含该序列的数组。对于字符串文字,数组元素的类型为char,并使用多字节字符序列的各个字节进行初始化。

(还提到了翻译阶段 6,即字符串文字连接阶段。即字符串文字连接保证在添加空终止之前发生。)

因此,mikroC PRO 似乎不合格/存在缺陷。肯定有很多有问题的嵌入式系统编译器。

  • @AdrianMole C17 6.5.3.4p2(sizeof),6.5.1p4(字符串lit expr),6.4.5p6(字符串lit lex,摘录在答案中)。文字没有“转换”为数组,而是从具有任何语义含义的第一点开始就是一个数组,就像“3”是一个“int”,而不是转换为一个。 (7认同)

chq*_*lie 15

\n

Is this one of the \'it depends\' things in C or is there a violation of the sizeof operator behavior?

\n
\n

The behavior is fully defined in the C Standard. Below are the relevant quotes from the C99 published standard, which were identical except for the section numbers in the C90 (ANSI C) version and have not been modified in essence in more recent version up to and including the upcoming C23 version:

\n

The __DATE__ and __TIME__ macros are specified by

\n
\n

6.10.8 Mandatory macros

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
__DATE__The date of translation of the preprocessing translation unit: a character string literal of the form "Mmm dd yyyy", where the names of the months are the same as those generated by the asctime function, and the first character of dd is a space character if the value is less than 10. If the date of translation is not available, an implementation-defined valid date shall be supplied.
__TIME__"hh:mm:ss"预处理翻译单元的翻译时间:函数生成的时间形式的字符串文字asctime。如果翻译时间不可用,则应提供实现定义的有效时间。
\n
\n

由上可知,如果翻译时间可用,则宏SAVEFILECHECK_COMPILE_DATE expands to 3 string literals for a total of 11+1+8 = 20 characters, hence 21 bytes including the null terminator. If the time of translation is not available, implementation defined valid dates and times must be used, hence the behavior must be the same.

\n
\n

5.1.1.2 翻译阶段

\n
    \n
  1. 相邻的字符串文字标记被连接起来。
  2. \n
  3. 分隔标记的空白字符不再重要。每个预处理标记都会转换为一个标记。对生成的标记进行句法和语义分析,并将其翻译为翻译单元。
  4. \n
\n
\n

因此,sizeof由 3 个相邻字符串文字构成的参数是无关紧要的,所有出现的sizeof operator in your examples get a single string literal argument in phase 7, then

\n
\n

6.5.3.4sizeof操作员

\n

4\xc2\xa0\xc2\xa0当sizeof应用于类型为 、 或 的操作数charunsigned char或其signed char限定版本)时,结果为1。当应用于具有数组类型的操作数时,结果是数组中的字节总数。

\n
\n

因此,示例中的所有 3 个输出都必须显示 21 个字节。您在mikroc编译器中发现了一个错误:您应该报告它并为您当前的项目找到解决方法。

\n

  • @chqrlie:它足够精确,尽管它可以更清楚地呈现。`sizeof` 需要一个数组,而不是“字符序列”。唯一存在的字符串文字数组已经包含 NUL。 (2认同)

sup*_*cat 10

sizeof正如其他人所指出的,字符串文字上的行为早已被标准化为产生一个比由此表示的字符串长度大一的值,而不是可以使用该字符串文字初始化的最小字符数组的大小。话虽如此,如果希望使代码即使与采用后一种解释的编译器兼容,我建议使用类似的表达式,(1-(sizeof "")+(sizeof "stringLiteral of interst"))这样可以允许代码与古怪的编译器正确运行,但避免牺牲与标准编译器的兼容性。

  • @MarkRansom:如果一个项目是为一个奇怪的编译器编写的,并且多年来一直在执行一项有用的任务,那么在迁移到新工具期间保持代码与旧编译器兼容可能会更容易确保这种迁移不会发生。不会产生意想不到的影响。 (4认同)
  • @AdrianMole:Lundin 的答案引用了有关将字符串文字转换为字符数组的相关文本。我的观点是,尽管人们不太可能遇到以标准指示之外的任何方式处理字符串的编译器(如果尚未这样做),但如果确实遇到这样做的编译器,那么这样的编译器必须明显存在;即使需要做一些不寻常的事情来使代码与这样的编译器兼容,这并不意味着该代码也不能与不那么不寻常的编译器兼容。 (4认同)
  • 这真是太聪明了,却又像罪恶一样丑陋。在这种情况下,“quicky”==“buggy”,我不确定是否值得为此采取解决方法。 (3认同)
  • @AdrianMole 是的,这个问题有“语言律师”标签,但这是后来由OP以外的人添加的。我同意引用会很好,但只要我一直在进行 C 和 C++ 编程,这就是我的行为。 (2认同)