C++17 中函数指针的求值顺序

Posted

技术标签:

【中文标题】C++17 中函数指针的求值顺序【英文标题】:Order of evaluation with function pointers in C++17 【发布时间】:2020-01-31 21:31:57 【问题描述】:

考虑以下 C++17 中的程序(以及注释中的替代程序):

#include<iostream>

void a(int) 
    std::cout << "a\n";


void b(int) 
   std::cout << "b\n";


int main() 
    using T = void(*)(int);

    T f = a;
    (T(f))((f=b,0)); // alternatively: f((f=b,0))

使用 -O2 选项,Clang 9.0.0 打印 a 和 GCC 9.2 打印 b。两者都警告我未排序的修改和访问f。见godbolt.org。

我的期望是这个程序具有明确定义的行为并将打印a,因为 C++17 保证调用 (T(f)) 的左侧表达式在之前排序对论点的任何评估。因为表达式(T(f)) 的结果是一个指向anew 指针,所以后面对f 的修改应该对调用完全没有影响。我错了吗?

如果我使用f((f=b,0)); 而不是(T(f))((f=b,0));,两个编译器都会给出相同的输出。在这里,我对未定义的行为方面有点不确定。这会是未定义的行为吗,因为f 在评估后仍然引用已声明的函数指针,该函数指针将通过对参数的评估进行修改,如果是这样,为什么会导致未定义的行为而不是打电话给b

关于 C++17 here 中非静态成员函数调用的求值顺序,我提出了一个相关问题。我知道编写这样的代码是危险且不必要的,但我想更好地了解 C++ 标准的细节。

编辑:在 Barry 提交的错误(请参阅下面的答案)得到修复后,GCC 主干现在也打印 a。不过,Clang 和 GCC 主干仍然会显示带有 -Wall 的误报警告。

【问题讨论】:

人们并没有非常彻底地检查编译器是否真的实现了所有新的评估规则顺序。如果您继续试验,请使用最新的编译器开发版本,并准备提交错误。 【参考方案1】:

C++17 规则是,来自[expr.call]/8:

后缀表达式表达式列表中的每个表达式和任何默认参数之前排序。参数的初始化,包括每个相关的值计算和副作用,相对于任何其他参数的初始化顺序是不确定的。

(T(f))((f=b,0)); 中,(T(f)) 在初始化来自(f=b, 0) 的参数之前排序。所有这些都是明确定义的,程序应该打印“a”。也就是说,它的行为应该像:

auto __tmp = T(f);
__tmp((f=b, 0));

即使我们将您的程序更改为有效,也是如此:

Tf(f=b, 0); // two parameters now, instead of one

f=b0 表达式的顺序不确定,但 Tf 仍然在两者之前排序,所以这仍然会调用 a

归档91974。

【讨论】:

感谢您的回答。 T(f)(f=b, 0); 不起作用,因为它将被视为对 f 的重新定义。我也对f((f=b, 0)) 的案例非常感兴趣。在那种情况下会发生什么不同的事情吗? 在 C++17 之前,各个参数和后缀表达式都是无序的。 T(f) 与它无关:f((f=b,0)) 具有相同的含义(再次从 C++17 开始)。

以上是关于C++17 中函数指针的求值顺序的主要内容,如果未能解决你的问题,请参考以下文章

关于使用共享指针的评估顺序

初始化列表中元素的求值顺序

欧拉线性筛 和 欧拉函数的求值

Java 表达式中子表达式的求值顺序

c语言 结构体的指针作为函数参数问题

typedef声明变量也是一种求值过程