printf函数args之间的顺序点;转换之间的顺序点重要吗? [英] Sequence Points between printf function args; does the sequence point between conversions matter?

查看:106
本文介绍了printf函数args之间的顺序点;转换之间的顺序点重要吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在此处读到有一个序列点:

I read here that there is a sequence point:

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

After the action associated with input/output conversion format specifier. For example, in the expression printf("foo %n %d", &a, 42), there is a sequence point after the %n is evaluated before printing 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);
}

我得到的不是我期望的:

Instead of what I expect I get:

12-0-12

12 - 0 - 12

表示没有为转换格式说明符创建一个 序列点.是 http://en.wikipedia.org 是错误的,还是我只是误解了一些东西,或者在这种情况下gcc不兼容(偶然) Visual Studio 2015会产生相同的意外结果)?

Meaning that there was not a sequence point created for the conversion format specifier. Is http://en.wikipedia.org wrong, or have I just misunderstood something, or is gcc non-compliant in this case (incidentally Visual Studio 2015 yields the same unexpected result)?

我知道your_function的参数被评估并分配给参数的顺序是不确定的.我不是在问为什么我的中间项是0.我是在问为什么其他两个项都都是12.

I understand that the order the arguments to your_function are evaluated and assigned to the parameters is undefined. I'm not asking about why my middle term is 0. I'm asking why the other two terms are both 12.

推荐答案

由于存在基于评论的讨论,因此提出了此问题

Because this question was asked because of a comment-based discussion here, I'll provide some context:

第一条注释:的操作顺序不能保证是您向函数传递参数的顺序.有些人(错误地)认为将对参数进行从右到左的求值,但是根据标准,行为是不确定的.

first comment: The order of operations is not guaranteed to be the order in which you pass arguments to the function. Some people (wrongly) assume that the arguments will be evaluated right to left, but according to the standard, the behaviour is undefined.

OP接受并理解这一点.重复your_function(++i, ++i)是UB的事实毫无意义.

The OP accepts and understands this. No point in repeating the fact that your_function(++i, ++i) is UB.

针对该评论:感谢您的评论,我看到printf可以按任何顺序求值,但是我理解这是因为printf参数是.您是说任何函数的参数都以任意顺序执行吗?

In response to that comment: Thanks to your comment I see that printf may be evaluated in any order, but I understood that to be because printf arguments are part of a va_list. Are you saying that the arguments to any function are executed in an arbitrary order?

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

OP asking for clarification, so I elaborated a bit:

第二条评论:是的,这正是我的意思.即使调用int your_function(int a, int b) { return a - b; }也不保证您传递的表达式将被从左到右求值.没有序列点(在该点上执行了先前评估的所有副作用).以此示例为例.嵌套调用是一个序列点,因此外部调用传递i+1(13),而内部调用的返回值(未定义,在这种情况下为-1,因为i++i显然为12、13 ),但不能保证总是如此

Second comment: Yes, that's exactly what I'm saying. even calling int your_function(int a, int b) { return a - b; } does not guarantee that the expressions you pass will be evaluated left to right. There's no sequence point (a point at which all side effects of previous evaluations are performed). Take this example. The nested call is a sequence point, so the outer call passes i+1 (13), and the return value of the inner call (undefined, in this case -1 because i++, i evaluates to 12, 13 apparently), but there's no guarantee that this will always be the case

这很清楚,这些类型的构造会触发所有功能的UB.

That made it pretty clear that these kinds of constructs trigger UB for all functions.

OP引用此:

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

After the action associated with input/output conversion format specifier. For example, in the expression printf("foo %n %d", &a, 42), there is a sequence point after the %n is evaluated before printing 42.

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

Then applies it to his snippet (prinf("%d - %d - %d\n", i, your_function(++i, ++i), i);) expeciting the format specifiers to serve as sequence points.
What is being referred to by saying "input/output conversion format specifier" is the %n specifier. The corresponding argument must be a pointer to an unsigned integer, and it will be assigned the number of characters printed thus far. Naturally, %n must be evaluated before the rest of the arguments are printed. However, using the pointer passed for %n in other arguments is still dangerous: it's not UB (well, it isn't, but it can be):

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

在调用函数之前有一个序列点,因此在%n&a设置为正确值之前,将对表达式100-a 进行求值.如果a未初始化,则100-a为UB.例如,如果a初始化为0,则表达式 的结果将为100.但是,总的来说,这种代码很麻烦.将其视为非常不好的做法,或更糟糕的是...
只需查看以下任一语句生成的输出即可:

There is a sequence point before the function is called, so the expression 100-a will be evaluated before %n has set &a to the correct value. If a is uninitialized, then 100-a is UB. If a is initialized to 0, for example, the result of the expression will be 100. On the whole, though, this kind of code is pretty much asking for trouble. Treat it as very bad practice, or worse...
Just look at the output generated by either one of these statements:

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

如您所见,nprintf内部中被重新分配,因此您不能在参数列表中使用其新值(因为有一个序列点).如果您希望n被就地"重新分配,实际上,您期望C跳出函数调用,评估其他参数,然后跳回调用.那是不可能的.如果将unsigned int a = 90;更改为unsigned int a;,则行为是不确定的.

In as you can see, n gets reassigned inside of printf, so you can't use its new value in the argument list (because there's a sequence point). If you expect n to be reassigned "in-place" you're essentially expecting C to jump out of the function call, evaluate other arguments, and jump back into the call. That's just not possible. If you were to change unsigned int a = 90; to unsigned int a;, then the behaviour is undefined.

现在,由于OP读取了序列点,因此他正确地注意到以下语句:

Now because the OP read up on sequence points, he correctly notices that this statement:

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

稍有不同:your_function(++i, ++i) 是一个序列点,而 保证 递增两次.此函数调用是一个序列点,因为:

Is slightly different: your_function(++i, ++i) is a sequence point, and guarantees that i will be incremented twice. This function call is a sequence point because:

在函数调用中输入函数之前.没有指定参数的计算顺序,但是此顺序点意味着在输入函数之前,它们的所有副作用都已完成

Before a function is entered in a function call. The order in which the arguments are evaluated is not specified, but this sequence point means that all of their side effects are complete before the function is entered

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

That means that, before printf is called, your_function has to be called (because its return value is one of the arguments for the printf call), and i will be incremented twice.
This could explain the output being "12 - 0 - 12", but is it guaranteed to be the output?

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

Technically, although most compilers will evaluate the your_function(++i, ++i); call first, the standard would allow a compiler to evaluate the arguments passed to sprintf left to right (the order isn't specified after all). So this would be an equally valid result:

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

尽管后者的输出是极不可能的(效率很低)

Although the latter output is extremely unlikely (it'd be very inefficient)

这篇关于printf函数args之间的顺序点;转换之间的顺序点重要吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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