混淆二维数组时strlen的意外优化

M.M*_*M.M 11 c arrays strlen multidimensional-array language-lawyer

这是我的代码:

#include <string.h>
#include <stdio.h>

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}
Run Code Online (Sandbox Code Playgroud)

在任何优化级别上使用gcc 8.3.0或8.2.1,但在我期望的时候-O0输出。编译器决定将限制于,因此永远不能等于或超过被除以的值。0 22 2strlenb[0]

这是我的代码中的错误还是编译器中的错误?

标准中并未明确阐明这一点,但是我认为指针来源的主流解释是,对于任何对象X,代码(char *)&X都应生成一个可以迭代整个对象的指针X-即使X碰巧具有子数组作为内部结构。

(奖金问题,是否有gcc标志来关闭此特定优化?)

Oli*_*ort 1

我检查了这一点,它在gcc 8.3-O1上重现,所以我只是在这里打开 gcc 优化标志列表并开始一一试验它们。事实证明,仅禁用稀疏条件常数传播可以使问题消失(幸运的是,如果一一测试没有结果,我计划测试几个标志)。-fno-tree-ccp

\n

然后我切换到-O2但没有擦除-fno-tree-ccp标志。又重现了。我说“好的”,然后开始测试其他-O2标志。再次看来,禁用单个值范围传播还会导致预期的2 2输出。\n然后我删除了第一个-fno-tree-ccp标志,但它再次开始复制。因此,-O2您可以指定-O2 -fno-tree-ccp -fno-tree-vrp使您的程序按预期工作。

\n

我没有删除这些标志,而是切换到了-O3那时。问题没有重现。

\n

所以 gcc 8.3 中的这两种优化技术都会导致这种奇怪的行为(也许它们在内部使用了一些常见的东西):

\n
    \n
  • 树上的稀疏条件常数传播
  • \n
  • 树上的值域传播
  • \n
\n

我不赞成解释那里发生了什么以及为什么发生,也许其他人可以解释。但可以肯定的是,您可以指定-fno-tree-ccp -fno-tree-vrp标志来禁用这些优化技术,以便您的代码按预期工作。

\n

\xe2\x80\x9c越努力,就越幸运。\xe2\x80\x9d \n\xe2\x80\x93 Samuel Goldwyn

\n

编辑

\n

正如@KamilCuk在问题评论中指出的那样,也会导致预期的行为,因此很可能在内置另一种优化的-fno-builtin-strlen组合中存在编译器错误,其目的是切断死代码,静态确定可能的表达式值并通过传播常量一个程序。我认为编译器很可能错误地将某些确定其实现中的字符串长度(可能与整数除法和/或二维数组结合)的东西视为死代码,并在编译时将其截断或计算为 0。因此,我决定稍微研究一下代码来检查理论并消除该错误的其他可能的“参与者”。我看到了这个最小的行为例子,它证实了我的想法:strlenstrlen

\n
int main()\n{\n    // note that "7" - inner arrays size, you can put any other number here\n    char b[23][7]; // local variable, no structs, no typedefs\n    memcpy(&b[0][0], "12345678123456781234", 21);\n\n    printf("%d\\n", strlen(&b[0][0]) / 8); // greater than that "7" !!!\n    printf("%d\\n", strlen(&b[0][0]) / 7);\n    printf("%d\\n", strlen(&b[0][0]) / 6); // less than that "7" !!!\n    printf("%d\\n", strlen(&b[0][0])); // without division\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

0

\n
\n
\n

0

\n
\n
\n

3

\n
\n
\n

20

\n
\n

我认为我们可以认为这是gcc中的一个错误。

\n

我认为这-fno-builtin-strlen是解决该问题的更好方法,因为它仅适用于所有优化级别,并且内置的strlen优化技术似乎不太强大,特别是如果您的程序不使用strlen()很多。仍然-fno-tree-ccp -fno-tree-vrp也是一个选择。

\n