printf中的序列点

时间:2016-01-06 16:16:28

标签: c++ c printf format-specifiers sequence-points

我读了here有一个序列点:

  

与输入/输出转换格式说明符关联的操作之后。例如,在表达式printf("foo %n %d", &a, 42)中,在打印%n之前评估42之后会有一个序列点。

但是,当我运行this code时:

int your_function(int a, int b) {
    return a - b;
}

int main(void) {
    int i = 10;

    printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
}

而不是我期望得到的:

  

12 - 0 - 12

意味着为转换格式说明符创建了一个序列点。 http://en.wikipedia.org是错误的,或者我只是误解了某些内容,或者在这种情况下gcc是否不合规(顺便说一句,Visual Studio 2015会产生同样的意外结果)?

修改

我理解评估your_function的参数的顺序并将其分配给参数是未定义的。我不是在问我为什么我的中期为0.我问为什么其他两个术语都是12。

3 个答案:

答案 0 :(得分:11)

我认为您误解了有关printf序列点(SP)的文字。它们在某种程度上是异常的,只有%n,因为这种格式说明符有副作用,需要对这些副作用进行排序。

无论如何,在printf()执行开始时和所有参数的评估之后都有一个SP。那些格式说明符SP都是之后的,所以它们不会影响你的问题。

在您的示例中,i的使用都在函数参数中,并且它们都不是用序列点分隔的。由于您修改了值(两次)并使用该值而没有插入序列点,因此您的代码为UB。

printf中关于SP的规则是什么,这个代码形式良好:

int x;
printf("%d %n %d %n\n", 1, &x, 2, &x);

即使x的值被修改两次。

但是这段代码是UB:

int x = 1;
printf("%d %d\n", x, ++x);

注意:请记住%n表示到目前为止写入的字符数被复制到相关参数指向的整数。

答案 1 :(得分:6)

由于基于评论的讨论here提出了这个问题,我提供了一些背景信息:

  

第一条评论:操作顺序保证是您将参数传递给函数的顺序。有些人(错误地)认为参数将从右到左进行评估,但根据标准,行为是未定义的。

OP接受并理解这一点。没有必要重复your_function(++i, ++i)是UB的事实。

  

回应该评论:感谢您的评论,我看到printf可以按任何顺序进行评估,但我理解这是因为printf参数是其中的一部分一个va_list。你是说任何函数的参数是以任意顺序执行的吗?

OP要求澄清,所以我详细说明了一下:

  

第二条评论:是的,这正是我所说的。甚至调用int your_function(int a, int b) { return a - b; }并不能保证您传递的表达式将从左到右进行计算。没有序列点(执行先前评估的所有副作用的点)。以this example为准。嵌套调用是一个序列点,因此外部调用传递i+1(13),以及内部调用的返回值(未定义,在本例中为-1,因为i++i显然评估为12,13,但是并不能保证这种情况总是如此

这清楚地说明这些类型的构造会触发所有函数的UB。

维基百科混淆

OP引用此信息:

  

与输入/输出转换格式说明符关联的操作之后。例如,在表达式printf(" foo%n%d",& a,42)中,在打印42之前评估%n之后有一个序列点。

然后将其应用于他的片段(prinf("%d - %d - %d\n", i, your_function(++i, ++i), i);),并将格式说明符视为序列点。
通过说"输入/输出转换格式说明符" 是指%n说明符。相应的参数必须是指向无符号整数的指针,并且它将被分配到目前为止打印的字符数。当然,必须在打印其余参数之前评估%n。但是,在其他参数中使用为%n传递的指针仍然很危险:它的 不是 UB(嗯,它不是,但它可以是):

printf("Foo %n %*s\n", &a, 100-a, "Bar");//DANGER!!

调用函数之前有一个序列点,因此在100-a设置{{1}之前评估表达式%n 到正确的值。如果&a未初始化,则a为UB。例如,如果将100-a初始化为0,则表达式的结果将为100.但总的来说,这种代码几乎要求麻烦。把它视为非常糟糕的做法,或者更糟糕......
只需查看其中一个语句生成的输出:

a

正如您所看到的,unsigned int a = 90; printf("%u %n %*s\n",a, &a, 10, "Bar");//90 Bar printf("%u\n", a);//3 printf("Foo %u %n %*s\n",a, &a, 10-a, "Bar");//Foo 3 Bar < padding used: 10 - 3, not 10 - 6 printf("%u\n", a);//6 n内被重新分配,因此您无法在参数列表中使用其新值(因为那里&# 39; sa序列点)。如果您希望printf被重新分配&#34;就地&#34;你基本上期望C跳出函数调用,评估其他参数,然后跳回到调用中。这是不可能的。如果您要将n更改为unsigned int a = 90;,则行为未定义。

关于unsigned int a;&#39;

现在因为OP读取了序列点,他正确地注意到这句话:

12

略有不同:printf("%d - %d - %d\n", i, your_function(++i, ++i), i); 一个序列点, 保证 {{ 1}}将递增两次。此函数调用是一个序列点,因为:

  

在函数调用中输入函数之前。未指定参数的计算顺序,但此序列点表示在输入函数之前所有副作用都已完成

这意味着,在调用your_function(++i, ++i)之前,i 被调用(因为它的返回值是printf调用的参数之一),your_function将增加两次 这个 可以 解释输出&#34; 12 - 0 - 12&#34; ,但它是否保证是输出?< / p>

没有

从技术上讲,虽然大多数编译器会先评估printf调用,但标准会允许编译器从左到右评估传递给i的参数(之后不会指定顺序) )。所以这将是一个同样有效的结果:

your_function(++i, ++i);

虽然后者的输出极不可能(效率非常低)

答案 2 :(得分:4)

在评估顺序和UB的C规则强烈影响(甚至阻止)对这个问题的明确答案。

评估顺序的指定规则如下:

  

C99第6.7.9节,第23页:23初始化列表的评估   表达式相对于彼此不确定地排序   因此,未指明任何副作用发生的顺序。

而且, this function call will exhibit undefined behavior

your_function(++i, ++i)

由于UB,加上评估顺序的规则,对以下预期结果的准确预测:

printf("%d - %d - %d\n", i, your_function(++i, ++i), i);

是不可能的。

<强> 修改
...我不是在问我为什么我的中期为0.我问为什么其他两个词都是12。

无法保证首先调用上述函数的三个参数中的哪一个。 (因为C&C的评估顺序规则)。如果首先评估中间函数,那么就在那时你已经调用了 Undefined Behavior 。谁能真正说为什么其他两个词是12?因为评估第二个参数时i发生的事情是任何人的猜测。