我试图更深入地理解C函数中的指针参数。我已经编写了一个测试程序,试图看到将单指针传递给函数的双指针和然后修改它。
我有一个有两个功能的程序。第一个函数modifyMe1
将一个指针作为参数,并将a属性更改为7.第二个函数modifyMe2
将双指针作为参数,并将a属性更改为7.
我预计第一个函数modifyMe1
将是"按值传递"也就是说,如果我传入了我的结构指针,C将创建它指向的数据的副本。对于后者,我正在做一个"传递参考"应该修改结构。
然而,当我测试这个程序时,两个函数似乎都在修改结构。我知道对于指针的性质我有一个误解是肯定的。有人可以帮我解决这个问题吗?
谢谢!
这就是我所拥有的:
#include <stdio.h>
#include <stdlib.h>
struct myStructure {
int a;
int b;
};
void modifyMe1(struct myStructure *param1) {
param1->a = 7;
}
void modifyMe2(struct myStructure **param1) {
(*param1)->a = 7;
}
int main(int argc, char *argv[]) {
struct myStructure *test1;
test1 = malloc(sizeof(test1));
test1->a = 5;
test1->b = 6;
modifyMe1(test1);
printf("a: %d, b: %d\n", test1->a, test1->b);
// set it back to 5
test1->a = 5;
printf("reset. a: %d, b: %d\n", test1->a, test1->b);
modifyMe2(&test1);
printf("a: %d, b: %d\n", test1->a, test1->b);
free(test1);
return 0;
}
我的输出是:
$ ./a
a: 7, b: 6
reset. a: 5, b: 6
a: 7, b: 6
答案 0 :(得分:18)
你可以用不同的方式传递参数C(Captain Obvious,yes)。
按价值计算。然后将其复制到堆栈。因此函数在函数框架中具有变量的局部副本。对参数的任何更改都不会更改传递的值。这就像“只读”模式
void fooByValue(myStructure_t arg) {
printf("passed by value %d %d\n", arg.a, arg.b);
arg.a = 0;
}
通过指针。然后传递此变量的地址副本(所以是的,它仍然按值传递,但是传递地址的值,而不是整个参数的值)。所以这就像“读写”模式。由于您可以通过其地址访问传递的变量,因此可以在函数外部更改此变量的值。
void fooByPtr(myStructure_t *arg) {
printf("passed by pointer %d %d\n", arg->a, arg->b);
arg->a = 0;
}
但是!你仍然无法修改指针。
因此,如果要修改指针,则应将指针传递给指针。这就像“读写修改”模式:
void fooByDblPtr(myStructure_t **arg) {
*arg = (myStructure_t*) malloc(sizeof(myStructure_t));
(*arg)->a = 10;
(*arg)->b = 20;
}
如果那只是指针那么就会出现内存泄漏:
void fooByDblPtr(myStructure_t *arg) {
arg = (myStructure_t*) malloc(sizeof(myStructure_t));
(arg)->a = 10;
(arg)->b = 20;
}
因为在这里你为地址的本地副本分配了新地址,这个参数将在函数完成后被销毁。
UPD。例如,我们有
void fooByPtr(myStructure_t *arg) {
printf("addr inside foo before %p\n", arg);
arg = (myStructure_t*) malloc(sizeof(myStructure_t));
(arg)->a = 10;
(arg)->b = 20;
printf("addr inside foo after %p\n", arg);
}
void main() {
myStructure_t *x = NULL;
x = malloc(sizeof(myStructure_t));
x->a = 10;
x->b = 20;
printf("x addr before = %p\n", x);
fooByPtr(x);
printf("x addr after = %p\n", x);
free(x);
}
分配内部函数内存,并将指针分配给局部变量。来电者仍然保持旧价值。函数调用后,我们丢失了内存地址,因此无法释放。
简短的结论:有一个简单的规则 - 如果需要更改参数,则将指针传递给它。所以,如果想要改变指针,则将指针传递给指针。如果想要更改双指针,则将指针传递给指向指针。
通过指针传递参数也要快得多,因为你不需要复制堆栈上的所有值(当然,如果value大于指向此值的指针,否则通过指针传递给只读毫无意义)。但它很危险,因为它可以在功能内部进行修改。因此,您可以使用const关键字
来保护定义它的参数void constFoo(const myStructure_t *arg) {
arg->a = 10; //compilation error
arg->b = 20; //compilation error
}
当您使用第三方库时,这非常有用:函数签名会告诉您函数是否可以修改您的参数。虽然const是可选的,但每次可能时都要编写const关键字
传递数组。通常,还会将数组大小(因此,size_t)作为参数发送。您将数组作为指针传递。
void foo (int *buf, size_t nbuf) {
....
}
有时您可以找到开发人员向对象而不是数组发送指针的代码,例如
void foo (int *buf, size_t size) {
size_t i;
for (i = 0; i < size; i++) {
printf("%d ", buf[i]);
}
}
int main(int argc, char **argv) {
int a = 10;
int buf[1] = { 10 };
foo(buf, 1);
foo(&a, 1);
}
在这种情况下,一个元素的数组和指向元素的指针的行为相似(但它们不相同)。
答案 1 :(得分:3)
使用常规参数,例如int
,您将获得本地副本
使用指针参数,例如int*
,您可以修改它指向的内容
使用双指针参数,比如int**
,您可以修改指针本身,即“重新指定”它。
答案 2 :(得分:1)
添加其他功能:
void modifyMe0(struct myStructure param1)
{
param1.a = 7;
}
这会按值传递结构。在函数中进行的修改不会反映在传递给modifyMe0()
的参数中。
像这样添加调用代码:
printf("Before 0: a = %d, b = %d\n", test1->a, test1->b);
modifyMe0(*test1);
printf("After 0: a = %d, b = %d\n", test1->a, test1->b);
请注意,调用代码中的before和after值是相同的。您还可以向modifyMeN()
函数添加打印,以证明其中的值已被修改。
当您将指向结构的指针传递给被调用函数时,可以修改调用函数中结构的值。当您通过值将结构传递给被调用函数时,不会修改调用函数中结构的值。
您可以创建另一个功能:
void modifyMe3(struct myStructure **p1)
{
free(*p1);
*p1 = malloc(sizeof(*p1));
(*p1)->a = -3;
(*p1)->b = -6;
}
像这样添加调用代码:
printf("Before 3: address = %p, a = %d, b = %d\n", (void *)test1, test1->a, test1->b);
modifyMe0(*test1);
printf("After 3: address = %p, a = %d, b = %d\n", (void *)test1, test1->a, test1->b);
请注意,调用modifyMe3()
后,结构的地址已更改。