C 发送参数的地址安全吗?

C 发送参数的地址安全吗?,c,function,arguments,parameter-passing,C,Function,Arguments,Parameter Passing,考虑以下代码段: void read_write(unsigned* ptr, bool r_or_w) { if (r_or_w) { *ptr = read_from_device_register(); } else { write_to_device_register(*ptr); } } unsigned read(void) { unsigned data = 0; read_write(&data,

考虑以下代码段:

void read_write(unsigned* ptr, bool r_or_w) {
    if (r_or_w) {
        *ptr = read_from_device_register();
    } else {
        write_to_device_register(*ptr);
    }
}

unsigned read(void) {
    unsigned data = 0;
    read_write(&data, 1);
    return data;
}

void write(unsigned data) {
    read_write(&data, 0); // <--- sending address of received argument
}

在这两种情况下,
数据
存储在堆栈上,只有在第一种情况下,
数据
存储在调用方的调用帧上,而不是作为局部变量存储在被调用方中。安全吗?有什么陷阱吗?如果有的话,它有多常见?

只要
read\u write
不将指针存储在比
write
框架中的指针对象
数据
寿命更长的地方,这里的生存期就没有问题。因此,从程序正确性的角度来看,这是安全的。事实上,给定的两个选项之间没有区别——传入参数的地址可以采用,就像局部变量的地址可以采用一样。至于在
write
的调用者的框架中修改变量的问题,这也是不相关的-
数据
是按值传递的

但是,如果函数经常调用或在紧循环中调用,则无符号整数的指针间接寻址可能会带来很大的开销


此外,从设计的角度来看,这似乎是一个有点难以理解的尴尬设计<代码>写入委托给
读写
,最终委托给
写入设备寄存器
。这有点难以直观地理解,而且不必要地将读写路径混合在一起,只是为了让它们再次分开;这可能是不安全的,因为与更简单的设计相比,代码更脆弱,更容易维护错误。

只要
read\u write
不将指针存储在比
write
框架中的指针对象
数据
寿命更长的地方,这里的生命周期就没有问题。因此,从程序正确性的角度来看,这是安全的。事实上,给定的两个选项之间没有区别——传入参数的地址可以采用,就像局部变量的地址可以采用一样。至于在
write
的调用者的框架中修改变量的问题,这也是不相关的-
数据
是按值传递的

但是,如果函数经常调用或在紧循环中调用,则无符号整数的指针间接寻址可能会带来很大的开销


此外,从设计的角度来看,这似乎是一个有点难以理解的尴尬设计<代码>写入委托给
读写
,最终委托给
写入设备寄存器
。这有点难以直观地理解,而且不必要地将读写路径混合在一起,只是为了让它们再次分开;这可能是不安全的,因为与更简单的设计相比,代码更脆弱,更容易维护错误。

语言规范没有说明参数存储的位置;关于哪个堆栈帧保存地址,您所描述的是一个实现细节,它不是语言的一部分。C甚至没有指定有一个堆栈,尽管它是典型的实现

变量的位置与回答这个问题无关,只是变量的生存期。函数参数的生存期与函数顶级块中的局部变量相同。您可以将地址传递给另一个函数,只要它只在
write()
返回之前使用,地址就有效。如果地址保存在其他数据结构中,则在
write()
返回后使用它会导致未定义的行为


因此,就您的问题而言,使用
&w_data
&data
没有区别。一个好的编译器优化器甚至可能为两个版本生成相同的代码。

语言规范没有说明参数存储在哪里;关于哪个堆栈帧保存地址,您所描述的是一个实现细节,它不是语言的一部分。C甚至没有指定有一个堆栈,尽管它是典型的实现

变量的位置与回答这个问题无关,只是变量的生存期。函数参数的生存期与函数顶级块中的局部变量相同。您可以将地址传递给另一个函数,只要它只在
write()
返回之前使用,地址就有效。如果地址保存在其他数据结构中,则在
write()
返回后使用它会导致未定义的行为

因此,就您的问题而言,使用
&w_data
&data
没有区别。一个好的编译器优化器甚至可能为两个版本生成相同的代码。

这是绝对安全的

即使
读写(&data,0)
使用指针更改
数据的值
,该更改不会影响
无效写入(无符号数据){..}
之外的任何内容。函数中的
无符号数据
与局部函数变量一样,只是它由调用函数时使用的参数值初始化

这是C中一个简单但重要的特性:参数总是按值传递。函数不能更改原始参数的值。如果参数是指针,则函数可以更改指向对象的值,但不能更改参数本身的值

例如:

void f1(int* p)
{
    *p = 42;
}

void f2(int n)
{
    printf("At start of f2 n is %d\n", n);
    f1(&n);
    printf("At end of f2 n is %d\n", n);
}

void f3(int* p)
{
    printf("At start of f3 *p is %d\n", *p);
    f2(*p);               
    printf("At end of f2 *p is %d\n", *p);
}
调用上面的链式

int x = 17;
printf("Before f3 x is %d\n", x);
f3(&x);                          
printf("After f3 x is %d\n", x);
将产生(我的评论):

绝对安全

即使
读写(&data,0)
使用指针更改
数据的值
,该更改不会影响
无效写入(无符号数据){..}
之外的任何内容。函数中的
无符号数据
与局部函数变量一样,只是它由调用函数时使用的参数值初始化

这是C中一个简单但重要的特性:参数总是按值传递。有趣的
int x = 17;
printf("Before f3 x is %d\n", x);
f3(&x);                          
printf("After f3 x is %d\n", x);
Before f3 x is 17
At start of f3 *p is 17
At start of f2 n is 17
At end of f2 n is 42     // notice: f1 changed the value of n inside f2
At end of f3 *p is 17    // but when f2 returns the value of the original x is the same
After f3 x is 17         // Reason: x and n are to different variables