3

当我使用 constexpr 函数时,我注意到一个奇怪的行为。我将代码简化为一个简化的示例。从两个不同的翻译单元(模块 A 和 B)调用两个函数。

#include <iostream>

int mod_a();
int mod_b();

int main()
{
    std::cout << "mod_a(): " << mod_a() << "\n";
    std::cout << "mod_b(): " << mod_b() << "\n";
    std::cout << std::endl;
    return 0;
}

这些模块看起来很相似。这是 mod_a.cpp:

constexpr int X = 3;
constexpr int Y = 4;

#include "common.h"

int mod_a()
{
    return get_product();
}

只有一些内部常数不同。这是 mod_b.cpp:

constexpr int X = 6;
constexpr int Y = 7;

#include "common.h"

int mod_b()
{
    return get_product();
}

两个模块都使用constexpr“common.h”中定义的通用函数:

/* static */ constexpr int get_product()
{
    return X * Y;
}

我很惊讶这两个函数都返回 12。由于#include指令(应该只包含一些源代码),我认为两个模块之间没有交互。当我get_product也定义为时static,行为如预期: mod_a()返回 12, mod_b()返回 42。

我还在https://www.youtube.com/watch?v=4pKtPWcl1Go上查看了 Jason Turner 的 C++ Weekly: Stop Using 'constexpr' (And Use This!) 第 312 集。

一般使用的建议static constexpr是一个很好的提示。

但我仍然想知道我在没有static关键字的情况下注意到的行为是否定义明确。还是UB?或者它是一个编译器错误?

除了constexpr函数,我还尝试了一个 C 风格的宏#define get_product() (X*Y),它也向我展示了预期的结果(12 和 42)。

小心

迈克尔

4

2 回答 2

4

该程序格式错误:X并且Y具有内部链接,因为它们是const命名空间范围内的变量。这意味着constexpr int get_product()(隐含inline的)的两个定义都违反了一个定义规则

只要满足以下所有条件,程序中就可以有多个以下定义:[...]、内联函数、[...]:

  • [...]
  • 从每个定义中查找名称会找到相同的实体(在重载解析之后),除了
    • 具有内部链接或没有链接的常量可以引用不同的对象,只要它们不被 ODR 使用并且在每个定义中具有相同的值

显然这些常数有不同的值。


正在发生的事情是两者mod_a都在运行时mod_b调用get_productget_product是隐式内联的,因此选择其中一个定义而丢弃另一个定义。gcc 似乎做的是采用找到的第一个定义:

$ g++ mod_a.cpp mod_b.cpp main.cpp && ./a.out
mod_a(): 12
mod_b(): 12
$ g++ mod_b.cpp mod_a.cpp main.cpp && ./a.out
mod_a(): 42
mod_b(): 42
$ g++ -c mod_a.cpp
$ g++ -c mod_b.cpp
$ g++ mod_a.o mod_b.o main.cpp && ./a.out
mod_a(): 12
mod_b(): 12
$ g++ mod_b.o mod_a.o main.cpp && ./a.out
mod_a(): 42
mod_b(): 42

好像get_productis not constexpr,因为它在运行时被调用。

但是,如果您要启用优化(或强制get_product()在编译时调用,如 with constexpr int result = get_product(); return result;),结果将如您“预期”的那样:

$ g++ -O1 mod_a.cpp mod_b.cpp main.cpp && ./a.out
mod_a(): 12
mod_b(): 42

(虽然这仍然是UB,正确的解决方法是制作函数static

于 2022-03-01T12:04:54.853 回答
2

该代码违反了单一定义规则(如果我错了,请语言律师纠正我)。

如果我单独编译代码,我会得到您期望的行为:

g++ -O1 -c main.cpp
g++ -O1 -c mod_a.cpp
g++ -O1 -c mod_b.cpp
g++ *.o
./a.out
> mod_a(): 12
> mod_b(): 42

如果我一次编译全部或激活链接时优化,UB 就会变得明显。

g++ -O1 *.cpp
./a.out
> mod_a(): 12
> mod_b(): 12

如何解决这个问题

声明它们是静态的,您走在正确的轨道上。更多的 C++-esce 将是一个匿名命名空间。您还应该将常量声明为静态或将它们放在命名空间中,而不仅仅是函数。

mod_a.cpp:

namespace {
constexpr int X = 3;
constexpr int Y = 4;
}

#include "common.h"

int mod_a()
{
    return get_product();
}

常见的.h:

namespace {
constexpr int get_product()
{
    return X * Y;
}
} /* namespace anonymous */

在我看来,更好的是:在打开的命名空间中包含 common.h。这使得声明之间的联系更加明显,并允许您拥有多个公共 get_products,每个命名空间一个。像这样的东西:

mod_a.cpp:


namespace {
constexpr int X = 3;
constexpr int Y = 4;

#include "common.h"
} /* namespace anonymous */

int mod_a()
{
    return get_product();
}
于 2022-03-01T12:05:37.210 回答