#include <stdio.h>
int a[] = {1,2};
void test(int in[3]){
//
}
int main() {
test(a);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码int in[3]中与int *in. 这个数字3并没有真正做任何事情,它甚至不是正确的大小,但即便如此,编译器也不会抱怨。那么这个语法在 C 中被接受还是我缺少一个功能是有原因的吗?
当数组参数声明包含一个常量大小时,它的唯一目的是作为读者的文档,通过向他们指示函数期望的数组大小。对于常量表达式n,编译器将数组声明转换为int in[n]to int *in,之后编译器没有任何区别,因此不会受到 的值的影响n。
最初在 C 中,函数参数由初始函数声明后的声明列表指定,例如:
int f(a, b, c)
int a;
float b;
int c[3];
{
… function body
}
Run Code Online (Sandbox Code Playgroud)
我推测在这些声明中允许数组大小仅仅是因为它们使用与其他声明相同的语法。编写排除大小的编译器代码和文档比简单地允许它们发生但忽略它们更难。当在函数原型 ( int f(int a, float b, int c[3])) 中声明参数类型时,我推测应用了相同的推理。
然而:
static,如int in[static n],则在调用函数时,相应的参数必须至少指向n元素,根据 C 2018 6.7.6.3 7. 编译器可以使用它进行优化。void test(int in[printf("Hi")]),那么在调用函数时GCC 10.2 和 Apple Clang 11.0 都会打印“Hi”。(但是,我不清楚 C 标准是否需要这种评估。)int x[3][4],将 的类型x调整为int (*)[4]。4 仍然是大小的一部分,并且对使用 的指针算术有影响x。struct foo x[3]如果struct foo尚未完全定义,则会产生诊断消息,但struct foo *x没有完全定义。在 C 语言中,指向一个结构的指针和指向同一数据结构的数组的指针没有区别。要获得下一个的起始地址,只需将指针增加数据的大小,并且由于不可能仅根据指针本身确定大小,因此您必须作为程序员提供它。
我们尝试修改一下程序
#include <stdio.h>
void test(int in[3]){
printf("%d %d,%d,%d\n",in[0],in[1],in[2],in[3]); // !Sic bug intentional
}
int main() {
int a[] = {1,2};
int b[] = {3,4};
test(a);
test(b);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
并运行它:
$ gcc pointer_size.c -o a.out && ./a.out
1 2,3,4
3 4,-1420617472,-1719256057
Run Code Online (Sandbox Code Playgroud)
在这种情况下,数组彼此背靠背放置,因此从 a 读取索引 2 和 3 将产生 b 的数据,当我们从 b 读取太多数据时,这些地址上存在的任何内容都将被读取。
即使迄今为止,这也是安全漏洞的一个非常常见的来源。