为什么未初始化而不是越界?
Why uninitialized instead of out-of-bounds?
在下面的代码中,为什么 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 突然发现了越界。
读取 b[9]
或 b[10]
时的行为是 未定义。
您的编译器正在发出警告(它不必发出警告),尽管警告文本有点误导,但从技术上讲并没有错。在我看来,这是相当聪明的。 (C 编译器不需要 来发出越界访问的诊断。)
关于 &b[9]
,编译器 不允许 取消引用它,并且必须将其评估为 b + 9
。您可以在数组末尾后设置一个指针。将指针设置为 &b[10]
的行为是 undefined.
我相信这里可能是这种情况:在第一个代码中,GCC 注意到您根本不需要整个 char 数组,只需要 b[9]
,因此它可以将代码替换为
char b_9; // = ???
printf("b[9] = %d\n", b_9);
现在,这是一个完全 合法的转换,因为当数组被越界访问时,行为完全未定义。只有在后一阶段,它才会注意到这个替代 b[9]
的变量未初始化,并发出诊断消息。
为什么我相信这个?因为如果我添加 just 任何代码 reference 数组在 memory 中的地址,例如 printf("%p\n", &b[8]);
任何地方,数组现在在内存中完全实现,编译器将诊断 数组下标超出数组边界.
我发现更有趣的是 GCC 根本不诊断越界访问,除非启用优化。这再次表明,无论何时编写新程序,都应该在启用优化的情况下编译它,以使错误高度可见,而不是通过调试模式隐藏它们 ;)
当您使用 -O2 编译代码时,该示例的琐碎性使该变量得到优化。所以警告是 100% 正确的
一些额外的实验结果。
使用 char b[9]
而不是 char b[]
似乎没有区别,gcc 仍然会警告与 char b[9]
相同的警告。
有趣的是,通过 struct
中的 "next" 成员初始化一次性元素 1) 确实消除了 "uninitialized" 警告,并且 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
在下面的代码中,为什么 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 突然发现了越界。
读取 b[9]
或 b[10]
时的行为是 未定义。
您的编译器正在发出警告(它不必发出警告),尽管警告文本有点误导,但从技术上讲并没有错。在我看来,这是相当聪明的。 (C 编译器不需要 来发出越界访问的诊断。)
关于 &b[9]
,编译器 不允许 取消引用它,并且必须将其评估为 b + 9
。您可以在数组末尾后设置一个指针。将指针设置为 &b[10]
的行为是 undefined.
我相信这里可能是这种情况:在第一个代码中,GCC 注意到您根本不需要整个 char 数组,只需要 b[9]
,因此它可以将代码替换为
char b_9; // = ???
printf("b[9] = %d\n", b_9);
现在,这是一个完全 合法的转换,因为当数组被越界访问时,行为完全未定义。只有在后一阶段,它才会注意到这个替代 b[9]
的变量未初始化,并发出诊断消息。
为什么我相信这个?因为如果我添加 just 任何代码 reference 数组在 memory 中的地址,例如 printf("%p\n", &b[8]);
任何地方,数组现在在内存中完全实现,编译器将诊断 数组下标超出数组边界.
我发现更有趣的是 GCC 根本不诊断越界访问,除非启用优化。这再次表明,无论何时编写新程序,都应该在启用优化的情况下编译它,以使错误高度可见,而不是通过调试模式隐藏它们 ;)
当您使用 -O2 编译代码时,该示例的琐碎性使该变量得到优化。所以警告是 100% 正确的
一些额外的实验结果。
使用 char b[9]
而不是 char b[]
似乎没有区别,gcc 仍然会警告与 char b[9]
相同的警告。
有趣的是,通过 struct
中的 "next" 成员初始化一次性元素 1) 确实消除了 "uninitialized" 警告,并且 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