为什么未初始化而不是越界?

时间:2018-07-17 12:38:45

标签: c gcc gcc-warning

在下面的代码中,b[9]为什么未初始化而不是越界?

#include <stdio.h>

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    printf("b[9] = %d\n", b[9]);

    return 0;
}

编译器调用:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:6:5: warning: ‘b[9]’ is used uninitialized in this function [-Wuninitialized]
     printf("b[9] = %d\n", b[9]);
% gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.6) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

更新:现在这很奇怪:

#include <stdio.h>

void foo(char *);

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    foo(&b[9]);
    foo(&b[10]);
    printf("b[9] = %d\n", b[9]);
    printf("b[10] = %d\n", b[10]);

    return 0;
}

编译此结果将导致警告:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:9:5: warning: array subscript is above array bounds [-Warray-bounds]
     foo(&b[10]);
     ^
foo.c:10:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[9] = %d\n", b[9]);
                             ^
foo.c:11:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[10] = %d\n", b[10]);

突然,gcc看到了它的边界。

4 个答案:

答案 0 :(得分:57)

我认为可能是这种情况:在第一个代码中,GCC注意到您根本不需要整个char数组,只需要b[9],因此可以用

替换代码。
char b_9; // = ???
printf("b[9] = %d\n", b_9);

现在,这是一个完全合法的转换,因为在超出范围访问数组时,行为是完全未定义的。然后,只有在后面的阶段中,它才会注意到该变量(它是b[9]的替代者)尚未初始化,并发出诊断消息。

为什么我相信这个?因为如果我添加 just 任何可以引用该数组在内存中的地址的代码,例如printf("%p\n", &b[8]);,那么该数组现在是完全在内存中实现,编译器将诊断数组下标超出数组范围


我发现更有趣的是,除非启用优化,否则GCC根本不会诊断出界访问。这再次表明,每当编写程序新程序时,都应使用启用了优化功能的程序对其进行编译,以使错误高度可见,而不是在调试模式下将其隐藏;)

答案 1 :(得分:17)

读取b[9]b[10]时的行为是未定义

您的编译器正在发出警告(不必这样做),尽管警告文本有些误导,但从技术上来说并不正确。我认为这很聪明。 ( 不需要C编译器来发出越界访问诊断。)

关于&b[9]不允许取消编译器的引用,必须将其评估为b + 9。您可以在数组末尾设置一个指针。设置指向&b[10]的指针的行为是 undefined

答案 2 :(得分:1)

一些其他实验结果。


使用char b[9]代替char b[]似乎没有什么区别,gcc仍然对char b[9]发出警告。

有趣的是,通过struct中的“ next”成员初始化一个通过的元素1)会使“未初始化”警告静音,并且2)不会警告要在数组外部访问。

#include <stdio.h>

typedef struct {
  char c[9];
  char d[9];
} TwoNines;

int main(void) {
  char b[9] = { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' };
  printf("b[] size %zu\n", sizeof b);
  printf("b[9] = %d\n", b[9]);   // 'b[9]' is used uninitialized in this function [-Wuninitialized]

  TwoNines e = { { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' }, //
                 { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' } };

  printf("e size %zu\n", sizeof e);
  printf("e.c[9] = %d\n", e.c[9]);   // No warning.

  return 0;
}

输出

b[] size 9
b[9] = 0
e size 18    // With 18, we know `e` is packed.
e.c[9] = 78  // 'N'

注意:
gcc -std = c11 -O3 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length = 0 -v -MMD -MP ...
gcc / gcc-7.3.0-2.i686

答案 3 :(得分:-2)

当使用-O2编译代码时,示例的琐碎性使该变量得以优化。因此警告是100%正确