'printf("%n %d", &a, a)' 定义明确吗?

Is 'printf("%n %d", &a, a)' well defined?

#include <stdio.h>

int main(void) {
    int i = 0;
    printf("abc %n %d", &i, i);
    printf("\n%d\n", i);
}

当我执行它时,我得到了以下结果。

abc  0
4

我认为这个结果是有意的

但是当我执行下一个时,我得到了不同的结果。

int main(void) {
    int i; // not initialize
    printf("abc %n %d", &i, i);
    printf("\n%d\n", i);
}

产生了:

abc  1
4

不知道为什么第一个printf()i的结果是1

甚至,我发现了更多奇怪的行为:

int main(void) {
    int sdf;
    printf("abc %n %d", &sdf, sdf);
    printf("\n%d\n", sdf);
    int i;
    printf("abc %n %d", &i, i);
    printf("\n%d\n", i);
}

使用此输出:

abc  1
4
abc  Random_Value
4

第一个总是显示 1 但其他显示随机值(我认为这是垃圾值)。

我认为垃圾值是有意的,但我不明白为什么第一个有不同的结果。

结果定义明确。 ii 的地址被传递给 printf。然后 printf 将值赋给 i。但是因为i是在它之前传递的,所以printf会在调用之前打印i的值。

后面的代码片段使用未初始化的变量,这个未确定的值将首先被打印出来。稍后的行为将与每个片段 1 相同。

未初始化的变量将具有未确定的值。如果变量的类型有陷阱表示,它也可以是一个 UB(这里不是这种情况)但是因为执行环境是相同的,所以很可能你会看到相同的值。如果您 运行 它在其他 OS、其他计算机上或将使用其他编译器,它们可能会有所不同。

Is printf(“%n %d”, &a, a) defined well in printf?

如果 a 的类型为 int(等价于 signed int)并且不是 const 限定的,则为是,否则为否。在well-defined的情况下,打印调用时a的值,调用后returns,a的值为0.

也许这里的关键点是函数调用的参数在进入被调用函数之前被评估,并且它们是按值传递的。在参数求值和函数 body 中第一条语句的执行之间存在一个序列点,因此 a 由调用函数读取并由 printf 写入这一事实确实不存在任何特定问题。

[...] when I execute next one, I got a different result.

int main(void)
{
  int i; // not initialize
  printf("abc %n %d", &i, i);
  printf("\n%d\n", i);
}
abc  1
4

I don't know why the result of 'i' in the first printf() is 1.

没有人这样做。一个既没有初始化也没有赋值的自动变量的值是indeterminate。与标题问题相同的注意事项适用于此处:printf 的参数列表中的表达式 iprintf 的任何部分执行之前被计算,因此 printf 稍后会为它赋值,不影响它接收到的值。

存储classauto的一个未初始化的局部整型变量,存储在栈区的值为undefined .出于这个原因,打印它,一个随机值是绝对期望的。

这就是为什么,在你的输出中

abc  1
4
abc  Random_Value
4

1 也是垃圾。

实际上它是堆栈中的第一个位置,它的值可能因系统and/or编译器的不同而不同。我的 猜测 是它的值为 1,因为它代表一个“幽灵 argc”,作为一个非常特殊的函数,即使定义该函数时没有参数,它也会存在,它的值为 1.

由于 argc 表示用于从命令行调用您的程序的参数数量(至少 1:可执行文件名称)有一种方法可以验证这个假设:以这种方式调用您的程序

executableName foo

这将使 argc 变为 2,因此第一个 printf 显示的值也应变为 2

出于好奇,我通过在我的机器上编译你的第二个示例来测试这个假设(DevC++ 和 gcc 编译器在 W10 64 位 OS 下)。我确认了两个陈述:

  1. 当发生未定义行为时,改变环境会导致输出改变
  2. argc存在于栈中,影响未初始化局部变量的初始值

执行后的输出uninitVars.exe

abc
0
abc
1

执行后的输出uninitVars.exe dog

abc
0
abc
2

执行后的输出uninitVars.exe dog cat

abc
0
abc
3

所以我的堆栈的前四个字节似乎总是设置为 0(它们是 return 值的位置吗?)而第二个 实际上是 argc,即使在main()原型中没有明确定义。


相反,第二个打印显示不同的值,因为它是在两次 printf 调用之后定义的,并且它们的执行在堆栈中写入几个字节(其中一些是地址,这解释了为什么值是总是不同的,因为进程的地址是虚拟的并且总是不同的)。

首先,你不能在同一行打印%n的结果,i会保留它之前的值。关于随机值,虽然您没有初始化 isdf,但它们有一个随机值(很可能是 0 但没有保证)。在C语言中,全局变量如果没有初始化就会有0值,但是局部变量会有未初始化的值(随机)。

由于所讨论的变量未被初始化,行为充其量是未指定的,最坏的情况是未定义的。

局部变量isdf是未初始化的,也就是说它们的值是不确定的。正式定义在 C standard 的第 3.19 节中,如下所示:

3.19.2

1 indeterminate value

either an unspecified value or a trap representation

3.19.3

1 unspecified value

valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance

2 NOTE An unspecified value cannot be a trap representation.

3.19.4

1 trap representation

an object representation that need not represent a value of the object type

这基本上意味着该值是不可预测的。事实上,在某些情况下,简单地读取一个不确定的值可能会导致 undefined behavior。如果不确定值恰好是如上定义的陷阱表示,就会发生这种情况。

如果相关变量的地址从未被占用,也可能是未定义的行为,但这在这种情况下不适用,因为您确实占用了地址。此行为记录在第 6.3.2.1p2 节中:

Except when it is the operand of the sizeof operator, the _Alignof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; additionally, if the lvalue has atomic type, the value has the non-atomic version of the type of the lvalue; otherwise, the value has the type of the lvalue. If the lvalue has an incomplete type and does not have array type, the behavior is undefined. If the lvalue designates 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 (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

因此假设您的实现没有陷阱表示,sdfi 的值未指定,这意味着它们可以是任何值,包括 0 或 1。例如,你得到值 1 和 sdfi 的(一些随机值)。当我 运行 相同的代码时,我得到这个:

abc  0
4
abc  0
4

如果我使用设置更高优化级别的 -O3 进行编译,我会得到:

abc  1446280512
4
abc  0
4

如您所见,运行读取未指定值的代码在不同的机器上可能会产生不同的结果,甚至在具有不同编译器设置的同一台机器上也会有不同的结果。

我得到的值0和你得到的值1没有什么特别的。它们与 1446280512 一样随机。