'printf(“%n%d”,&a,a)是否定义正确?

时间:2020-06-28 13:23:32

标签: c initialization printf

#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

我不知道为什么i在第一个printf()中的结果是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,而其他一个则显示随机值(我认为这是垃圾值)。

我认为垃圾值是有目的的,但我不明白为什么第一个垃圾值会有所不同。

5 个答案:

答案 0 :(得分:3)

结果定义明确。 ii的地址被传递到printf。然后,printf将值分配给i。但是由于i是在其之前传递的,所以printf将在调用之前打印i的值。

后面的代码段使用未初始化的变量,并且该不确定的值将首先打印。以后的行为将与每个被截断的1相同。

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

答案 1 :(得分:3)

存储在堆栈区域中的存储类auto的未初始化局部整数变量的值为undefined。因此,绝对需要一个随机值。

这就是为什么在您的输出中

abc  1
4
abc  Random_Value
4

1也是垃圾。

实际上,它是堆栈中的第一个位置,并且其值在更改系统和/或编译器时可能会有所不同。我的猜测的值为1,因为它表示一个“鬼魂argc”,它是一个非常特殊的函数,即使该函数定义为不带参数,其值仍然存在是1。

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

executableName foo

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

出于好奇,我通过在计算机上编译第二个示例(在W10 64位OS下使用gcc编译器的DevC ++)来测试了这一假设。我确认了两个声明:

  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(它们是返回值的位置吗?),而第二个实际上是 argc,即使未在main()原型中明确定义。


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

答案 2 :(得分:2)

printf(“%n %d”, &a, a)在printf中定义是否正确?

如果a的类型为int(相当于signed int),并且没有const限定,则为是,否则为否。在定义明确的情况下,将在调用时打印a的值,并在调用返回后,a的值将为0。

这里的重点也许是函数调用的参数在进入被调用函数之前先经过评估,然后按值传递。在函数主体中对参数进行评估与执行第一条语句之间存在一个序列点,因此,a由调用函数读取并由printf写入的事实并不存在任何特殊之处。问题。

[...]当我执行下一个时,我得到了不同的结果。

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

我不知道为什么第一个printf()中的“ i”结果为1。

没有人这样做。既未初始化也未分配给它的自动变量的值为 indeterminate 。此处的注意事项与标题问题中的注意事项相同:i的参数列表中的表达式printf在执行printf的任何部分之前求值,因此{{1} }稍后将为其分配一个值,不会影响它接收到的值。

答案 3 :(得分:2)

在不初始化相关变量的情况下,行为最多是未指定的,最坏的情况是未定义的。

局部变量isdf尚未初始化,这意味着它们的值不确定。正式定义在C standard的3.19节中,如下所示:

3.19.2

1 确定值

未指定值或陷阱表示

3.19.3

1 未指定值

本国际标准未规定任何要求的相关类型的有效值 在任何情况下都选择哪个值

2 注意未指定的值不能是陷阱表示。

3.19.4

1 陷阱表示

不需要表示对象类型值的对象表示形式

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

如果有问题的变量从未使用过地址,这也可能是未定义的行为,但是在这种情况下,这并不适用,因为您确实使用了地址。 6.3.2.1p2节中记录了此行为:

除非它是sizeof运算符的操作数,否则 _Alignof运算符,一元&运算符,++运算符, --运算符,或.运算符的左操作数或 赋值运算符,没有数组类型的左值 转换为存储在指定对象中的值 (并且不再是左值);这称为 lvalue 转化。如果左值具有限定类型,则该值具有 左值类型的非限定版本;另外, 如果左值具有原子类型,则该值具有非原子版本 左值的类型;否则,该值具有 左值的类型。如果左值具有不完整的类型并且 没有数组类型,行为是不确定的。 如果是左值 指定一个自动存储期限的对象,该对象可以 已用register存储类声明(从来没有 地址),并且该对象未初始化(未声明) 使用初始化程序,并且没有分配给它 使用前执行),则行为是不确定的。

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

abc  0
4
abc  0
4

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

abc  1446280512
4
abc  0
4

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

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

答案 4 :(得分:0)

首先,您无法在同一行中打印%n的结果,i将保留其先前的值。关于随机值,虽然您尚未初始化isdf,但它们具有随机值(很可能为0,但没有保证)。在C语言中,如果全局变量未初始化,则其值为0,但局部变量将具有未初始化的值(随机)。