当前位置:  开发笔记 > 编程语言 > 正文

printf中的序列点

如何解决《printf中的序列点》经验,为你挑选了2个好方法。

我在这里读到有一个序列点:

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

但是,当我运行此代码时:

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.



1> rodrigo..:

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

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

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

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

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

即使值x被修改两次.

但是这段代码是UB:

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

注意:请记住,这%n意味着到目前为止写入的字符数被复制到关联参数指向的整数.



2> Elias Van Oo..:

因为这个问题被问,因为基于注释的讨论,在这里,我将提供一些背景:

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

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

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

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

第二条评论:是的,这正是我所说的.甚至调用int your_function(int a, int b) { return a - b; }并不能保证您传递的表达式将从左到右进行评估.没有序列点(执行先前评估的所有副作用的点).就拿这个例子.嵌套调用是一个序列点,所以外部调用传递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 将对表达式求值.如果未初始化,那么是UB.例如,如果初始化为0,则表达式的结果将为 100.但总的来说,这种代码几乎是在寻找麻烦.把它当作非常糟糕的做法,或者更糟糕...... 只要看看这些陈述中的任何一个产生的输出:%n&aa100-aa

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被重新分配里面printf,所以你不能在参数列表中使用其新的价值(因为有一个序列点).如果你希望n"就地"重新分配,你基本上期望C跳出函数调用,评估其他参数,然后跳回到调用中.那是不可能的.如果要更改unsigned int a = 90;unsigned int a;,则行为未定义.


关于12's

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

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

略有不同:your_function(++i, ++i) 一个序列点,并保证i将增加两倍.此函数调用是一个序列点,因为:

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

这意味着,之前printf被调用,your_function 被称为(因为它的返回值是参数为一个printf呼叫),并且i将增加两倍.
可以解释输出为"12 - 0 - 12",但它是否保证是输出?

没有

从技术上讲,虽然大多数编译器会your_function(++i, ++i);首先评估调用,但标准将允许编译器评估sprintf从左到右传递的参数(毕竟没有指定顺序).所以这将是一个同样有效的结果:

10 - 0 - 12
//or even
12 - 0 - 10
//and
10 - 0 - 10
//technically, even this would be valid
12 - 0 - 11

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

推荐阅读
拾味湖
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有