C的函数, 如果它有参数. 参数是怎么传递的.
有这么几点必须明白:
1. 参数传递时, 函数体内部将在内存stack区域新建作用在这个函数内部的本地变量.
如 void test1 (char * a) , 这里 a 是函数的本地变量.
2. 传递的是值, 而不是变量.
如 test1(b), 函数体里面的 a 是拷贝了b这个变量的值, 而不是b这个变量.
来做个测试就会明白 :
[root@digoal zzz]# cat a.c
#include <stdio.h>
#include <string.h>
int main() {
typedef struct fish {
int age;
char name[10];
} fish;
char * gt1 = "abcdefg";
char gt2[10] = "abcdefg";
int gt3 = 100;
fish gt4 = {20, "linux"};
void test1 (char * t1) {
fprintf(stdout, "&t1:%p, t1:%p, >1:%p, gt1:%p\n", &t1, t1, >1, gt1);
}
void test2 (char t2[]) {
fprintf(stdout, "&t2:%p, t2:%p, >2:%p, gt2:%p\n", &t2, t2, >2, gt2);
}
void test3 (int t3) {
fprintf(stdout, "&t3:%p, >3:%p\n", &t3, >3);
}
void test4 (struct fish t4) {
fprintf(stdout, "&t4:%p, >4:%p\n", &t4, >4);
}
test1(gt1);
test2(gt2);
test3(gt3);
test4(gt4);
return 0;
}
结果 :
[root@digoal zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
&t1:0x7fff08f00b10, t1:0x400748, >1:0x7fff08f00ad0, gt1:0x400748 // 字符串在这里是常量放在constants内存区域, 所以地址比较小. 而变量放在stack区域, 所以地址比较大 .
&t2:0x7fff08f00b10, t2:0x7fff08f00aec, >2:0x7fff08f00aec, gt2:0x7fff08f00aec // 这里要特别注意, 函数体内存储的是个指针变量, 不是数组, 这个指针指向了外部的数组 .
&t3:0x7fff08f00b1c, >3:0x7fff08f00ae8
&t4:0x7fff08f00b00, >4:0x7fff08f00ad8
// 这里我分别测试了指针变量,数组,int型以及结构体 .
结果表明 :
1. 指针变量作为参数类型时, 传递的是指针存储的地址值. 而指针变量的地址是不一样的, 因为在函数体内的t1的地址与gt1地址不一致.
2. 数组作为参数类型时(这里要注意, 数组作为参数变量, 这个变量在函数体内部是指针, 也就是失去了数组的特性),
传递的是外部数组gt2的地址, 所以地址 (t2 = >2 = gt2), (因为数组变量名在编译时替换成地址). 参数变量的地址与参数变量存储的地址不一致. 也说明了参数变量虽然是数组. 但是显然它在函数体内其实是指针. 如果是数组的话, &t2 应该等于 t2.
3. int作为参数类型时, 传递的是值. 可以看到&t3 不等于 >3
4. 结构体作为参数类型时, 传递的也是值. 可以看到&t4 不等于 >4 .
5. &t1 = &t2 又是为什么呢, 前面说了 函数体内部的变量都放在stack里面, 当函数调用结束就会释放掉, 所以test1 执行完再执行test2 是有可能申请到同一片stack里面的地址的.
另外要说的是 , 由于是拷贝, 所以我们要特别注意变量存储的是什么东西. 如参数类型是指针, 拷贝过去就是这个指针存储的地址. 所以他们会指向同一个地方。
所以如果结构体中如果有使用数组存储的, 或者指针存储的元素, 他们拷贝的内容是大不一样的.
例如 :
[root@digoal zzz]# cat a.c
#include <stdio.h>
#include <string.h>
int main() {
typedef struct fish {
int age;
char name[10];
char * nick;
} fish;
fish gt4 = {20, "linux", "world"};
void test4 (struct fish t4) {
fprintf(stdout, "&(t4.name):%p, &(gt4.name):%p, t4.name:%p, gt4.name:%p\n", &(t4.name), &(gt4.name), t4.name, gt4.name);
fprintf(stdout, "&(t4.nick):%p, &(gt4.nick):%p, t4.nick:%p, gt4.nick:%p\n", &(t4.nick), &(gt4.nick), t4.nick, gt4.nick);
}
test4(gt4);
return 0;
}
结果
[root@digoal zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
&(t4.name):0x7fffed837484, &(gt4.name):0x7fffed8374a4, t4.name:0x7fffed837484, gt4.name:0x7fffed8374a4
&(t4.nick):0x7fffed837490, &(gt4.nick):0x7fffed8374b0, t4.nick:0x4006e8, gt4.nick:0x4006e8
来解释一下 :
1. &(t4.name):0x7fffed837484, &(gt4.name):0x7fffed8374a4 这两个指存放name这个元素的地址. 它们不相等, 是因为函数函数传参是新建本地变量与值拷贝的过程. 所以t4和gt4是放在两块内存区域的, 因此t4和gt4的name它们的存放地当然不一样.
2. t4.name:0x7fffed837484, gt4.name:0x7fffed8374a4 这两个不一样, 因为t4在函数内部是一个全新的结构体变量, 所以t4.name 存储的是数组本身, 而没有退化成指针, 所以拷贝的是数组的内容. 因此可以看出&(t4.name) = t4.name ; &(gt4.name) = gt4.name. 因为数组即地址(多次提到).
3. &(t4.nick):0x7fffed837490, &(gt4.nick):0x7fffed8374b0 这两个不相等 . 和第一条的解释一样.
4. t4.nick:0x4006e8, gt4.nick:0x4006e8 这两个指的是nick这个指针存放的内容转成地址打印出来, 当然是相同的.
最后, 脑子里有一副内存的图就比较好理解了.