ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

int a[10] 传进函数后,那个 10 去哪了?数组作参数会退化成指针

int a[10] 传进函数后,那个 10 去哪了?数组作参数会退化成指针 目录作为参数的数组声明先验证一下hello 和 hello[0] 真的一样吗编译器会替你改写形参后果一函数里永远不知道数组有多长后果二数组能被函数改掉特殊的反例argv作为参数的数组声明作者在 3.3 节开头就把结论摆出来了在 C 语言中我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数那么数组名会立刻被转换为指向该数组第 1 个元素的指针。他给了个例子。有这么个字符数组char hello[] hello;把它交给printfprintf(%s\n, hello);和写成这样完全等效printf(%s\n, hello[0]);请注意完全等效这四个字。不是差不多不是结果一样是编译器把它们当成同一个东西。先验证一下hello和hello[0]真的一样吗#include stdio.h int main(void) { char hello[] hello; printf(hello %p\n, (void *)hello); printf(hello[0] %p\n, (void *)hello[0]); printf(hello %p\n, (void *)hello); printf(hello 1 %p\n, (void *)(hello 1)); printf(hello 1 %p\n, (void *)(hello 1)); printf(sizeof(hello) %d\n, (int)sizeof(hello)); printf(sizeof(hello) %d\n, (int)sizeof(hello)); return 0; }hello 00000074D32FFDA0 hello[0] 00000074D32FFDA0 hello 00000074D32FFDA0 hello 1 00000074D32FFDA1 hello 1 00000074D32FFDA6 sizeof(hello) 6 sizeof(hello) 8地址每次运行都不同看差值就行。前三个是同一个地址。但1之后分道扬镳hello 1挪了1个字节 —— 它把自己当成指向 char 的指针hello 1挪了6个字节 —— 它把自己当成指向整个 char[6] 的指针sizeof(hello) 6hello是char[6]—— hello 五个字符加上结尾那个\0。sizeof作用在数组上给的是整个数组占的字节数6 × 1 6。sizeof(hello) 8hello的类型是char (*)[6]—— 指向整个数组的指针。指针就是指针x64 下不管指向什么都是 8 字节跟数组多大没关系。地址值相同类型不同步长就不同。书里那个完全等效说的是前两个hello和hello[0]可千万别把hello也算进去。编译器会替你改写形参既然是自动转换那函数形参里写的数组会变成什么书里的答案因此将数组作为函数参数毫无意义。所以C 语言中会自动地将作为参数的数组声明转换为相应的指针声明。也就是说像这样的写法int strlen(char s[])与下面的写法完全相同int strlen(char *s)完全相同到什么程度我写了四个函数分别用四种写法#include stdio.h void h5(int a[5]) { printf(int a[5] : sizeof %d\n, (int)sizeof(a)); } void h10(int a[10]) { printf(int a[10] : sizeof %d\n, (int)sizeof(a)); } void hu(int a[]) { printf(int a[] : sizeof %d\n, (int)sizeof(a)); } void hp(int *a) { printf(int *a : sizeof %d\n, (int)sizeof(a)); } int main(void) { int b[10]; h5(b); h10(b); hu(b); hp(b); return 0; }四个全一样都是 8x64 下指针的大小。方括号里写 5、写 10、什么都不写编译器看都不看——它就是int *a。那个10没被吃掉它压根就没进过编译。这也回答了开头的疑问sizeof(a)在函数里得 8因为a已经不是数组是个指针了。那就会出现一些问题后果一函数里永远不知道数组有多长回到开头那段代码。main里的a是货真价实的int[10]sizeof(a) 40除以sizeof(a[0]) 4得 10。一进函数a退化成int *sizeof(a) 88 / 4 2。所以 C 函数从来没法知道传进来的数组有多长——这不是没实现是信息在传参那一刻就丢了。这就是为什么 C 标准库里的函数都要额外带一个长度memcpy(dst, src, n)、fwrite(ptr, size, count, fp)、qsort(base, nmemb, size, cmp)。这不是设计得啰嗦是不得不。自己写函数也一样void zero(int a[], int n) /* n 必须自己传进来 */ { int i; for (i 0; i n; i) a[i] 0; }后果二数组能被函数改掉这是数组退化成指针的另一面也是最容易懵的地方——C 是按值传递的可数组传进去之后函数却能把外面的数据改掉。因为传的不是数组是地址。函数拿到地址就能直接动原始内存。#include stdio.h void setint(int x) { x 99; } void setarr(int a[]) { a[0] 99; } int main(void) { int n 1; int a[3] {1, 2, 3}; setint(n); setarr(a); printf(after setint: n %d\n, n); printf(after setarr: a[0] %d\n, a[0]); return 0; }setint里把形参改成 99外面n还是 1——这是正常的按值传递改的是副本。setarr里把a[0]改成 99外面真的变了。因为这里根本没有副本函数手上就是main里那个数组的首地址。所以数组不是按值传递的这个说法更准确的说法是数组传参传的是地址改地址指向的内容就是改原数组。特殊的反例书里专门留了句警告C 程序员经常错误地假设在其他情形下也会有这种自动地转换。extern char *hello;这个语句与下面的语句有着天壤之别extern char hello[];extern 是声明不是定义告诉编译器这个变量 / 函数在别的地方已经定义好了直接用不要在这里分配内存。同一个名字一个是指针变量一个是数组。数组名的自动转换只发生在表达式里用到数组名的场合不发生在声明里。这两个东西内存里的样子完全不同char *hello是一个 8 字节的指针char hello[]是一串紧挨着的字符。声明错了读出来的就是垃圾。编程者的本意是想要将这个数组通过extern来引申到这个文件里但是char *hello是引入了一个指针char hello[]才是真正地引入了数组这样写才能实现编程者的真正意图在声明里并不会发生转化。书说这一节在 4.5 节详细讲argv的例子书里最后举了main的例子一个常见的例子就是函数 main 的第二个参数main(int argc, char *argv[])这种写法与下面的写法完全等价main(int argc, char **argv)实测一下这个等价#include stdio.h void g1(char *a[]) { printf(char *a[] : sizeof %d\n, (int)sizeof(a)); } void g2(char **a) { printf(char **a : sizeof %d\n, (int)sizeof(a)); } int main(void) { char *v[3] {aa, bb, cc}; g1(v); g2(v); return 0; }char *a[]里的数组照例被无视剩下的就是char **a。书里对这两种写法的取舍很实用前一种写法强调的重点在于 argv 是一个指向某数组的起始元素的指针该数组的元素为字符指针类型。因为这两种写法是等价的所以读者可以任选一种最能清楚反映自己意图的写法。等价但表意不同。想强调这是一串字符串写char *argv[]想强调这是一个指向指针的指针写char **argv。怎么选看自己选想让别人一看就懂的地方。
返回列表