向函数传递指向本地变量的指针:是否安全?

26
例如:
void func1(){
    int i = 123;
    func2(&i);
}
void func2(int *a){
    *a = 456;
}

func1调用func2时,传递给func2的是指向本地变量的指针--该指针指向堆栈。这符合C语言规则吗?

谢谢。


3
在变量被销毁后使用指向该变量的指针是不安全的。但在这里你没有这样做。 - user253751
6个回答

25

i的作用域是func1,并且它在调用func2之后仍然存在。所以很安全。


7
正如之前的大部分回答所述,对于您的特殊情况,在func2()中传递指针是完全安全的。
然而,在真实的软件中,我认为这是有害的,因为您无法控制func2()对变量的操作。 func2()可能会创建一个参数的别名,以便在稍后的时间异步使用它。但是在此别名稍后被使用时,本地变量int i可能已经消失。
因此,从我的角度来看,传递指向本地(自动)变量的指针是极其危险的,应该避免。如果您在func1()中声明变量为static int i;,则可以这样做。
在这种情况下,可以确保不会回收和重写i的内存。但是,在并发环境中需要设置一些互斥锁来进行访问控制。
为了说明这个问题,这里是昨天我在客户那边做软件测试时遇到的一些代码。是的,它会崩溃...
void func1()
{
  // Data structure for NVMemory calls
  valueObj_t NVMemObj;

  // a data buffer for eeprom write
  UINT8 DataBuff[25];
  // [..]
  /* Assign the data pointer to NV Memory object */
  NVMemObj.record = &DataBuff[0];
  // [..]
  // Write parameter to EEPROM.
  (void)SetObject_ASync(para1, para2, para3, &NVMemObj);
  return;
}

void SetObject_ASync(para1, para2, para3, valueObj_t *MemoryRef)
{
  //[..]
  ASyncQueue.CommandArray[ASyncQueue.NextFreeEntry].BufferPtr  = MemoryRef->record;
  //[..]
  return;
}

在这种情况下,当使用ASyncQueue.CommandArray [ASyncQueue.NextFreeEntry] .BufferPtr指针将数据存储到EEPROM中时,DataBuff中的数据已经不存在了。
要修复此代码,至少需要声明static UINT8 DataBuff [25];。另外,还应该考虑声明static valueObj_t NVMemObj,因为我们不知道调用的函数对该指针做了什么。
简而言之: 总之 尽管在C语言中这是合法的,但我认为将自动变量的指针传递给函数调用是有害的。您永远不知道(通常也不想知道)调用的函数对传递的值做了什么。当所调用的函数建立别名时,问题就出现了。
这只是我的两分钱。

5
"func2() 可能会创建一个别名来异步使用其参数,以便在稍后的时间点使用。" 但是同样的情况也可能发生在你将 malloc 应用于内存并将其传递给函数时……它可能会创建一个别名并尝试在调用者稍后释放内存之后访问它。这里的重点不是调用者做错了什么,而是 被调用函数 保留对自己参数的引用(在哪里?全局变量中吗?),并在 后续调用 中重复使用它们。同时,请考虑到这是更安全的起点,因为无需释放。简而言之,自动变量=好,函数保留指向其参数的指针=不好。" - aaa90210
@aaa90210 但是这个例子没有使用malloc分配的内存。它使用了一个static缓冲区,在整个进程的生命周期中都保证存在。所以我同意,malloc或C++的new是危险的。但是带有互斥锁的静态变量不是。 - deLock
@deLock 但是func2()并不知道它接收到的指针参数是指向静态分配、自动分配还是动态分配的内存,而且它所做的事情只在这三种情况中的第一种情况下安全(如果是指向静态分配的内存的指针,将别名指针放在全局范围或其他地方是安全的)。因此,我认为不负责任的地方在于func2()假设它可以使用别名指针参数,而不是func1()传递其局部变量的地址。 - 6equj5

2

可以安全地传递指向本地变量的指针,但是不能从函数返回指向自动本地变量的指针。


2

是的,你的代码是安全的。

只要对象的生命周期没有结束,像你这样传递局部变量是安全的。


1

这对C语言的规则是安全的吗?

你所做的是安全的,因为局部变量仍然有效且在作用域内。在其作用域外访问局部变量是未定义的行为,但这完全没有问题。


1

在你的情况下,只要 i 仍然有效,你可以安全地使用 &i

现在我们可以看到,i 的生命周期延续到 func1() 结束。由于 func2() 是从 func1() 调用的,而 func1() 尚未执行完成,所以 i 仍然有效。

这就是为什么通常允许将本地变量的地址传递给另一个函数(变量的生命周期尚未结束),但是不允许在 return 后返回本地变量的地址(函数的本地变量在 return 后立即消失)。

TL;DR:如本例所示,你可以安全地将 &i 作为 func2() 的参数使用。


范围 应用于标识符(而不是变量),它表示标识符可见的位置,所以 ifunc2 中不在作用域内。也许你要找的是 生命周期 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接