为什么这段代码在C语言中没有产生越界访问?

4
在尝试比较 C99 和 Rust 之间的一些差异时,我写了以下代码:
typedef enum {
    NICKLE, DIME, QUARTER, DOLLAR,
} Denom;

static const int cents[] = {
    [NICKLE] = 5,
    [DIME] = 10,
    [QUARTER] = 25,
    [DOLLAR] = 100,
};

int main () {
    printf( "Result %d\n", cents[DIME] );
}

这个代码符合预期,现在我想展示一种越界访问,但是当我这样做时

typedef enum {
    NICKLE, DIME, QUARTER, DOLLAR, ZOD = 20394
} Denom;

我原本期望

printf( "Result %d\n", cents[ZOD] );

为了显示 cents + ZOD 的访问权限,但是没有成功。
 mov eax, 0
 mov esi, eax
 lea rdi, str.Result__d        ; 0x5570bf1ed020 ; "Result %d\n"
 mov eax, 0
 call sym.imp.printf           ; int printf(const char *format)

为什么会将esi设置为0,并且这段代码会输出什么?
Result 0

-Wall -Wextra -Wpedantic 也没有报错。GCC 是否提供控制此行为的标志?它是否知道这是越界的,如果是,它不会发出警告(或者可以发出警告)吗?


3
似乎编译器可以在静态确定它是未定义行为。它可以自由地进行任何操作。(例如,查看使用-fsanitize = undefined的无条件调用。) - Ry-
1
在线文档更好。 - S.S. Anne
很奇怪extern const int cents[4];会产生警告,但是static却不会。如果gcc也能对静态数组发出警告就好了。 - KamilCuk
@EvanCarroll:我不知道gcc,但clang肯定可以。 - Ry-
@Ry- Clang会警告所有可能的问题。只需使用-Weverything -std=<c standard> -pedantic编译您的项目并尝试运行即可。 - S.S. Anne
显示剩余7条评论
3个回答

1

是的,在特定情况下,GCC可以警告这一点(请参见@Acorn的答案)。

有一个运行时未定义行为检查器,{{link1:-fsanitize=undefined}},如果您进行某些类型的未定义行为(例如访问超出数组边界),则应该会出现错误。

由于这是未定义的行为,编译器可以自由地做任何它想做的事情。这意味着它可以将esi设置为零、您密码的十六进制表示或任何其他内容。


这很尴尬,为了捕获在编译时被捕获的错误,GCC可以在编译时修复,我必须启用运行时未定义行为检查器,它必须生成与通常生成的不同的代码,在命中代码路径后通知我。 - Evan Carroll
@EvanCarroll 是的。 - S.S. Anne

1
补充另一个答案,注意其他编译器在给出问题中的源代码时会发出警告。 例如,Clang:
<source>:15:28: warning: array index 20394 is past the end of the array (which contains 4 elements) [-Warray-bounds]
    printf( "Result %d\n", cents[ZOD] );
                           ^     ~~~
<source>:7:1: note: array 'cents' declared here
static const int cents[] = {
^

除了icc:

<source>(15): warning #175: subscript out of range
      printf( "Result %d\n", cents[ZOD] );

对于GCC,你可以使用-Warray-bounds选项,它在-Wall -O2中包含。特别地,你需要-ftree-vrp(一种优化方式,可以消除不必要的范围检查,例如数组边界检查)。
然而,由于某些原因,GCC只会对非const和非static的数组发出警告。如果尝试:
#include <stdio.h>

typedef enum {
    NICKLE, DIME, QUARTER, DOLLAR, ZOD = 20394
} Denom;

int cents[] = {
    [NICKLE] = 5,
    [DIME] = 10,
    [QUARTER] = 25,
    [DOLLAR] = 100,
};

int main () {
    printf( "Result %d\n", cents[ZOD] );
}

你会看到它警告了这个问题:

<source>: In function 'main':
<source>:15:5: warning: array subscript 20394 is above array bounds of 'int[4]' [-Warray-bounds]
   15 |     printf( "Result %d\n", cents[ZOD] );
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:7:5: note: while referencing 'cents'
    7 | int cents[] = {
      |     ^~~~~

自GCC 4.4.7以来,我至少可以看到警告出现了!


谢谢!我已经将这个内容加入到我的回答中了。如果您不满意,我可以撤销我的编辑。 - S.S. Anne
请注意,警告甚至出现在 GCC 4.4.7 上,无需使用最新的 GCC。 - Acorn
我知道。只是警告的格式而已。我在我的32位Gentoo Prefix中有GCC 9,但我几乎从不使用它。 - S.S. Anne
好的。完成。限制 - S.S. Anne
1
评论长度限制。请参见https://meta.stackoverflow.com/questions/389069/ui-bug-when-comment-is-not-long-enough。 - S.S. Anne
显示剩余3条评论

0

C语言定义不强制对数组访问进行任何类型的边界检查 - 编译器或运行时环境没有任何要求以任何方式处理越界访问。 行为是未定义的。

如果在翻译期间可以检测到越界访问,各个编译器可能会发出诊断信息,并且随着时间的推移,越来越多的编译器正在这样做(正如其他帖子所说,gcc和clang确实这样做,但默认情况下不会)。

如果直到运行时才能检测到 - 好吧,C语言没有任何结构化异常处理机制,因此即使由运行时环境发出信号,也没有好的处理问题的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接