考虑以下 C++ 代码:
std::uint32_t func(std::uint32_t* p1, std::uint64_t* p2) {
*p2 = *p1;
return *p1;
}
Run Code Online (Sandbox Code Playgroud)
在 Clang 上编译会-O3
产生以下反汇编结果(GCC 类似):
func(unsigned int*, unsigned long*):
mov eax, dword ptr [rdi]
mov qword ptr [rsi], rax
ret
Run Code Online (Sandbox Code Playgroud)
(现场演示: https: //godbolt.org/z/vKPvT7o51)
由于严格的别名规则,编译器假设p1
和p2
不指向相同的内存位置,因此不需要*p1
从内存中重新加载 return 语句。到目前为止一切顺利,优化正在按我的预期进行。
现在考虑一段类似的代码,其中复制是通过以下方式完成的memcpy()
:
std::uint32_t func(std::uint32_t* p1, std::uint64_t* p2) {
std::memcpy(p2, p1, sizeof(std::uint32_t));
return *p1;
}
Run Code Online (Sandbox Code Playgroud)
反汇编(在 Clang 和 GCC 上相同):
func(unsigned int*, unsigned long*):
mov eax, dword ptr [rdi] …
Run Code Online (Sandbox Code Playgroud) 我遇到过以下代码,其行为得到了 GCC、Clang 和 MSVC 的一致认可:
#include <concepts>
template<typename T>
auto foo(T);
template<typename U>
struct S {
template<typename T>
friend auto foo(T) {
return U{};
}
};
S<double> s;
static_assert(std::same_as<decltype(foo(42)), double>);
Run Code Online (Sandbox Code Playgroud)
现场演示: https: //godbolt.org/z/hK6xhesKM
foo()
在全局命名空间中声明并推导返回类型。S<U>
提供了 via 友元函数的定义foo()
,它返回类型为 的值U
。
我期望的是,当S
用 实例化时U=double
,它的定义foo()
被放入全局命名空间中U
并被替换,由于友元函数的工作方式,有效地如下所示:
template<typename T>
auto foo(T);
S<double> s;
template<typename T>
auto foo(T) {
return double{};
}
Run Code Online (Sandbox Code Playgroud)
因此我期望foo()
的返回类型是double
,并且以下静态断言应该通过:
static_assert(std::same_as<decltype(foo(42)), double>);
Run Code Online (Sandbox Code Playgroud)
然而,实际发生的情况是,所有三个编译器对此行为都持不同意见。
正如我所期望的,GCC 通过了静态断言。
Clang 失败了静态断言,因为它似乎相信 …
考虑以下代码:
struct Foo {
float f;
Foo& operator+=(Foo rhs) {
Foo operator+(Foo, Foo);
*this = *this + rhs;
return *this;
}
};
inline Foo operator+(Foo lhs, Foo rhs) {
return Foo{lhs.f + rhs.f};
}
Run Code Online (Sandbox Code Playgroud)
当且仅当operator+(Foo, Foo)
定义为 时inline
,GCC 和 MSVC 会给出“未定义对 operator+(Foo, Foo) 的引用”链接器错误,而 Clang 没有问题。请参阅此处以获取实时示例。
我认为这段代码是有效的;inline
函数的-ness 应该没有任何影响,因为函数不需要像inline
定义为那样声明inline
(无论如何,函数不能inline
在块范围内声明)。
请注意,如果在 的定义之前operator+(Foo, Foo)
改为声明(为非inline
)Foo
,则所有 3 个编译器都接受它。
但是,标准中的诸如链接之类的问题很棘手,所以我知道它也可能是未定义的行为或“格式错误,无需诊断”,而 GCC 和 MSVC 恰好会出错。
那么这个代码有效吗?哪个编译器是正确的?
考虑以下结构:
struct alignas(8) Foo {
char data[8];
};
Run Code Online (Sandbox Code Playgroud)
标准是否保证这一点sizeof(Foo) == 8
?
从外行人的角度来看,这应该是正确的,因为 8char
不需要任何填充即可对齐为 8。并且在 GCC、Clang 和 MSVC 上的实践中似乎也是如此(现场演示)。
然而,填充和对齐是一件棘手的事情,我找不到任何严格禁止实现添加无关填充的信息 - 即也sizeof(Foo) == 16
可能吗?
到目前为止我发现了以下内容:
sizeof(char[8]) == 8
是有保证的(见这里),因此sizeof(Foo) >= 8)
Foo
没有前导填充,因为它是标准布局类型(请参见此处)std::bit_cast
- 显然认为没有填充 -请参阅这篇文章(有趣的是,如果这个事实不正确,则似乎意味着结构可以具有任意大的大小,这有点奇怪。)