将未初始化的变量传递给另一个函数UB [英] Is passing an uninitialized variable to another function UB

查看:149
本文介绍了将未初始化的变量传递给另一个函数UB的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不知道只是将未初始化的变量传递给函数会导致未定义的行为吗?



对我来说看起来很奇怪。



假设我们有下面的代码:

  void open_db(db * conn)
{
//打开数据库连接并将其存储在conn
}

int main()
{
db * conn;
open_db(conn);
}

这对我来说完全合法。它不取消引用未初始化的变量,也不会中继其状态。它只是将未初始化的指针传递给另一个函数,该函数通过 operator new 或类似的东西在其中存储一些数据。





对于其他类型如 int

也是如此。 code>?

  void foo(int bar)
{
// ...
}

int main()
{
int bar;
foo(bar); // UB?
}


解决方案

类型的参数没有关系。 C99的相关位是:当你用自动存储持续时间声明一个变量,但不初始化它,它的值是 indeterminate (6.2.4p5,6.7.8p10);任何使用不确定的值都会引起未定义的行为(J.2参考6.2.4,6.7.8和6.8) 1



即使它不是UB(例如,如果 conn 已经初始化),这个代码不会有你似乎期望它有的效果。如所写, open_db 不能修改其调用者中的变量 conn



无论 conn 是否被初始化,你的代码有一点变化是有效的, :

  void open_db(db ** conn)
{
* conn = internal_open_db
}

int main()
{
db * conn;
open_db(& conn);
}

c $ c>& 是语言中很少的一些东西,在应用到未初始化的变量时,不会引发未定义的行为,因为它不会读取。它只确定变量的内存位置。这是一个确定的值,可以安全地传递到 open_db (但是注意它的类型签名已经改变:它现在接收一个指针指向一个指针 db open_db 现在可以使用指针解除引用操作符,



在C ++中,这种非常常见的模式接收一些语法糖:

  void open_db(db *& conn)
{
conn = internal_open_db();
}

int main()
{
db * conn;
open_db(conn);
}

将第二个星号变更为&符号会将 conn 参数设为 open_db 现在是指向指针的引用,它仍然是指向底层指针的指针,但编译器填充&






1 对于我的同学语言律师:附录J是非规范性的,我找不到任何规范性的陈述支持它的断言,使用不确定的值总是 UB。 (这可能会有帮助,如果我可以找到一个定义,这意味着使用值的第一位。我相信的意图是触发6.3.2.1p2左值转换,但我不认为



不确定值的定义是未指定的值或陷阱表示;使用未指定的不会挑衅UB。使用陷阱表示确实会引起UB,但不是所有类型都具有陷阱代码。 C11但不是C99,在6.3.2.1p2中有一个句子,它非常秃头地说明了如果[代码读取一个值]自动存储持续时间的对象,它可以使用寄存器存储类(从未取得其地址),并且该对象未初始化,行为未定义 - 但请注意,它在这里不使用术语不确定值,并且它将规则限制到变量但是,C编译器绝对会将读取任何未初始化的变量作为UB,无论其类型是否具有陷阱reps或其地址是否已被占用,以及J.2当然反映了委员会的意图,第7条中的一些例子,其中单词不确定单独出现以指出读取一些变量是UB。


I wonder is it true that just passing an uninitialized variable to a function results in an undefined behavior?

It seems really weird for me.

Suppose that we have the following code:

void open_db(db* conn)
{
  // Open database connection and store it in the conn
}

int main()
{
  db* conn;
  open_db(conn);
}

It seems perfectly legal to me. It doesn't dereference an uninitialized variable nor it doesn't relay on its state. It just passes an uninitialized pointer to another function that stores some data in it via operator new or something like this.

If it's UB, could you quote the exact place where the Standard says so?

And is it also true for other types like int?

void foo(int bar)
{
  // ...
}

int main()
{
  int bar;
  foo(bar); // UB?
}

解决方案

It is UB, and the type of the argument does not matter. The relevant bits of C99 are: when you declare a variable with "automatic storage duration" but don't initialize it, its value is indeterminate (6.2.4p5, 6.7.8p10); any use of an indeterminate value provokes undefined behavior (J.2 refers to 6.2.4, 6.7.8, and 6.8)1.

And even if it wasn't UB (for instance if conn had been initialized)., this code would not have the effect you seem to expect it to have. As written, open_db cannot modify the variable conn in its caller.

A slight variation on your code is valid whether or not conn is initialized, and does do what you expect it to do, though:

void open_db(db **conn)
{
  *conn = internal_open_db();
}

int main()
{
  db *conn;
  open_db(&conn);
}

The address-of operator, unary &, is one of the very few things in the language that does not provoke undefined behavior when applied to an uninitialized variable, because it does not read the value of the variable. It only determines the memory location of the variable. That is a determinate value, that can safely be passed to open_db (but note that its type signature has changed: it is now receiving a pointer to a pointer to a db. And open_db can now use the pointer-dereference operator, unary *, to write a result into the variable.

In C++ only, this very common pattern receives a bit of syntactic sugar:

void open_db(db *&conn)
{
  conn = internal_open_db();
}

int main()
{
  db *conn;
  open_db(conn);
}

Changing the second star to an ampersand makes the conn argument to open_db now a "reference" to a pointer. It's still a pointer to a pointer "under the hood", but the compiler fills in the & and * operators for you as necessary.


1 For my fellow language lawyers: Annex J is non-normative, and I can't find any normative statement backing up its assertion that using an indeterminate value is always UB. (It might help if I could find a definition of what it means to "use a value" in the first place. I believe the intent was anything that triggers 6.3.2.1p2 "lvalue conversion", but I don't think that's ever actually stated.)

The definition of an "indeterminate value" is "an unspecified value or a trap representation"; using an unspecified value does not provoke UB. Using a trap representation does provoke UB, but not all types have trap reps. C11, but not C99, has a sentence in 6.3.2.1p2 that states quite baldly "if [the code reads a value from] an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized, the behavior is undefined" -- but note that it doesn't use the term-of-art "indeterminate value" here, and it restricts the rule to variables whose address is not taken.

However, C compilers absolutely do treat reading any uninitialized variable as UB regardless of whether its type has trap reps or whether its address has been taken, and J.2 certainly reflects the intent of the committee, as do a number of examples in clause 7 where the word "indeterminate" appears solely to point out that reading some variable is UB.

这篇关于将未初始化的变量传递给另一个函数UB的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆