为什么此代码的结果在使用和不使用“-fsanitize=undefined,address”时会有所不同?

Suo*_*Suo 5 c clang

我发现这段代码使用“-fsanitize=undefined,address”和不使用它会产生不同的结果。

int printf(const char *, ...);
union {
  long a;
  short b;
  int c;
} d;
int *e = &d.c;
int f, g;
long *h = &d.a;
int main() {
  for (; f <= 0; f++) {
    *h = g;
    *e = 6;
  }
  printf("%d\n", d.b);
}
Run Code Online (Sandbox Code Playgroud)

命令行是:

$ clang -O0 -fsanitize=undefined,address a.c -o out0
$ clang -O1 -fsanitize=undefined,address a.c -o out1
$ clang -O1 a.c -o out11
$ ./out0
6
$ ./out1
6
$ ./out11
0
Run Code Online (Sandbox Code Playgroud)

Clang 版本是:

$ clang -v
clang version 13.0.0 (/data/src/llvm-dev/llvm-project/clang 3eb2158f4fea90d56aeb200a5ca06f536c1df683)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /data/bin/llvm-dev/bin
Found candidate GCC installation: /opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7
Selected GCC installation: /opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
Found CUDA installation: /usr/local/cuda, version 10.2
Run Code Online (Sandbox Code Playgroud)

操作系统和平台是:

CentOS Linux release 7.8.2003 (Core).0, x86_64 GNU/Linux
Run Code Online (Sandbox Code Playgroud)

我的问题:

  1. 我的代码有问题吗?在 C 中获取联合的多个成员的地址是否无效?
  2. 如果我的代码有问题,我如何让 LLVM(或 GCC)警告我?我使用过 -Wall -Wextra 但 LLVM 和 GCC 没有显示任何警告。

Eri*_*hil 1

为了便于阅读,我将重写代码:

\n
int printf(const char *, ...);\n\nunion\n{\n    long  l;\n    short s;\n    int   i;\n} u;\n\nlong *ul = &u.l;\nint  *ui = &u.i;\n\nint counter, zero;\n\nint main(void)\n{\n    for (; counter <= 0; counter++)\n    {\n        *ul = zero;\n        *ui = 6;\n    }\n    printf("%d\\n", u.s);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

u.s这里唯一有问题的代码是在 中使用printf,whenu.s不是存储的联合体的最后一个成员。这是由 C 2018 6.5.2.3 定义的,其中表示 的值u.s是指定成员的值,注释 99 澄清了这意味着,如果s不是最后用于存储值的成员,则相应的字节将被重新解释为short。这是众所周知的。

\n

其他代码很普通:*ul = zero;将值存储在联合成员中。不存在违反别名的情况,因为ul指向 along并用于访问 a long*ui = 6;将值存储在另一个联合成员中,也不是别名违规。

\n

用于表示 an 中的 6 的特定字节int是在排序和填充位方面由实现定义的。然而,无论它们是什么,无论有没有 Clang\xe2\x80\x99s \xe2\x80\x9csanitization\xe2\x80\x9d,它们都应该是相同的,并且在优化级别 0 和 1 中都应该相同。因此,相同的结果应该是在所有编译中获得。

\n

这是一个编译器错误。

\n

我同意其他评论并回答说这可能是 C 标准中的一个缺陷,因为它使得别名规则基本上毫无用处。尽管如此,示例代码符合 C 标准的要求,并且应该按照描述的方式工作。

\n

  • 我怀疑这是一个编译器错误。不能指望编译器会记住指针“ul”和“ui”指向“union”内部的事实,因此这些指针可能会产生别名。在我看来,编译器只能在直接访问“union”或通过指向整个“union”的指针访问“union”时知道正在访问“union”,而不是通过指向单个成员的指针。然而,据我所知,ISO C 标准并没有明确解决这种情况,包括最新的 C23 草案。 (2认同)
  • 是的,我和@AndreasWenzel 在一起。考虑一个像 `int func(short *ps, int *pi) { *pi = 0; *ps = 7;返回*pi;}`。我认为严格别名规则的要点是编译器可以优化 `*pi` 的重新加载并无条件返回 0,事实上 gcc、clang、icc 都这样做。但是 Eric 的论点似乎是说 `func (&amp;u.s, &amp;u.i)` 必须返回 7(假设实现具有典型的小端表示)。如果是这样,那么这就是一个漏洞,吞噬了严格的别名规则。 (2认同)