考虑这个程序:
#include <stdio.h>
struct S {
S() { print(); }
void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
Run Code Online (Sandbox Code Playgroud)
据我所知,这里只S构建了一个对象.没有副本省略:首先没有副本被删除,事实上,如果我明确删除了副本和/或移动构造函数,编译器继续接受该程序.
但是,我看到打印了两个不同的指针值.发生这种情况是因为我的平台的ABI在CPU寄存器中返回了一些可复制的类型,例如这个类型,因此ABI无法避免复制.clang即使在完全优化掉函数调用时也会保留这种行为.如果我给出S一个非平凡的复制构造函数,即使它不可访问,那么我确实看到相同的值打印两次.
print()在构造期间发生的初始调用,即在对象生命周期开始之前,但this在构造函数内部使用通常是有效的,只要它不以需要构造完成的方式使用 - 没有强制转换为例如派生类 - 据我所知,打印或存储其值不需要构造完成.
标准是否允许此程序打印两个不同的指针值?
注意:我知道该标准允许该程序打印相同指针值的两个不同表示,从技术上讲,我没有排除这一点.我可以创建一个避免比较指针表示的不同程序,但它会更难理解,所以我想尽可能避免这种情况.
我想知道编译器是否会在32位和64位系统上使用不同的填充,因此我在一个简单的VS2019 C ++控制台项目中编写了以下代码:
struct Z
{
char s;
__int64 i;
};
int main()
{
std::cout << sizeof(Z) <<"\n";
}
Run Code Online (Sandbox Code Playgroud)
我对每个“平台”设置的期望:
x86: 12
X64: 16
Run Code Online (Sandbox Code Playgroud)
实际结果:
x86: 16
X64: 16
Run Code Online (Sandbox Code Playgroud)
由于x86上的存储字大小为4个字节,因此这意味着它必须以i两个不同的字存储字节。所以我认为编译器将以这种方式进行填充:
struct Z
{
char s;
char _pad[3];
__int64 i;
};
Run Code Online (Sandbox Code Playgroud)
所以我可以知道背后的原因是什么?
我一直在阅读Clang源代码,发现了一些关于ARM C++ ABI的有趣内容,我似乎无法理解其中的理由.从ARM ABI文档的在线版本:
这个ABI要求C1和C2构造函数返回它(而不是void函数),这样C3构造函数可以尾调用C1构造函数,C1构造函数可以尾调用C2.
(对于非虚拟析构函数也是如此)
我不知道是什么C1,C2以及C3在这里引用.这一节,就是要的§3.1.5从通用(即安腾)ABI的修改,而这部分(至少在这个网上verison)简单地说:
构造函数返回void结果.
无论如何,我真的无法弄清楚这是什么目的:如何使构造函数返回此允许尾部调用优化,以及在什么情况下?
到目前为止,我可以说,构造函数可以尾部调用另一个具有相同this返回值的唯一时间是具有单个基类的派生类,一个简单的构造函数体,没有具有非平凡构造函数的成员,并且没有虚拟表指针.实际上,使用void返回来优化尾部调用似乎实际上更容易,而不是更难,因为这样可以消除单个基类的限制(在多基类的情况下,this指针从最后调用的构造函数不会是this派生对象的指针).
我在这里错过了什么?ARM调用约定是否有this必要使返回成为必要?
有一个类似的帖子涵盖了常规寄存器.NEON寄存器怎么样?据我记得,函数调用必须保留上半部分或下半部分寄存器.我无法在任何地方找到这些信息,有人可以澄清一下吗?
谢谢
从AAPCS,§5.1.2.1VFP寄存器使用约定(VFP v2,v3和高级SIMD扩展):
我有一些静态库,我不是它的所有者,用旧版本的g ++ 4.3.2编译(c ++ 11/c ++ 0x未激活).
当我使用g ++ 4.6(没有c ++ 11)编译我的代码并使用g ++ 4.6将它与这些静态库链接时,它链接很好,我似乎没有在运行时遇到任何问题(虽然没有测试所有内容).我很想认为前向兼容性还可以.
现在我想用gcc 4.8和c ++ 11编译我的代码,并且仍然将它与那些相同的,而不是重新编译的静态库链接起来.
ABI中的ABI更改只是链接向前兼容性的问题,还是会出现向后兼容性问题?
考虑以下示例:
struct vector {
int size() const;
bool empty() const;
};
bool vector::empty() const
{
return size() == 0;
}
Run Code Online (Sandbox Code Playgroud)
生成的汇编代码vector::empty(通过 clang,经过优化):
push rax
call vector::size() const
test eax, eax
sete al
pop rcx
ret
Run Code Online (Sandbox Code Playgroud)
为什么要分配堆栈空间?它根本没有被使用。该push和pop可以省略。MSVC 和 gcc 的优化构建也为此功能使用堆栈空间(请参阅有关Godbolt 的内容),因此必须有一个原因。
考虑 Linux 系统上的以下两个文件:
使用消息.cpp
#include <iostream>
extern const char* message;
void print_message();
int main() {
std::cout << message << '\n';
print_message();
}
Run Code Online (Sandbox Code Playgroud)
libmessage.cpp
#include <iostream>
const char* message = "Meow!"; // 1. absolute address of string literal
// needs runtime relocation in a .so
void print_message() {
std::cout << message << '\n';
}
Run Code Online (Sandbox Code Playgroud)
我们可以将use_message.cpp编译为目标文件,将libmessage.cpp编译为共享库,并将它们链接在一起,如下所示:
$ g++ use_message.cpp -c -pie -o use_message.o
$ g++ libmessage.cpp -fPIC -shared -o libmessage.so
$ g++ use_message.o libmessage.so -o use_message
Run Code Online (Sandbox Code Playgroud)
的定义message …
我有两个场景.假设我有3个导出C++符号的共享库,每个库都使用VS7.1,VS8和VS9构建.我在VS9中编译了所有3个.出于某种原因,这是有效的.我不需要在VS9中为VS9链接器重新编译前两个库,以成功找到符号并链接它们.
现在,如果我有一个只使用C语法(extern"C")导出符号的库,这是一样的吗?我听说有人说ABI for C是标准化的,所以在某种程度上保证你可以在所有版本的Visual Studio中使用Visual Studio 8中编译的C库.
基本上,所有这些事情的结合令人困惑.我不确定在不同版本的Visual Studio之间链接C++和基于C的共享库(使用相应的导入库)之间有什么保证.我想听听关于任何其他版本的Visual Studio上C 和 C++导入或静态库的向前/向后兼容性的一致意见.
这对我来说是因为我使用的闭源库是在Visual Studio .NET 2003(VS7.1)中编译的.我的团队认为这将我们锁定到VS 7.1编译器,但是我已经在VS8和VS9中测试了这些库,甚至是VS2010,它们链接得很好.但是,我不确定这个内在的危险.请注意,所讨论的库具有C变体和C++变体.基本上,C变量是标准C导出,C++库是C库和导出类的抽象.
我们最近收到了一份报告,因为GCC 5.1,libstdc ++和Dual ABI.似乎Clang不知道GCC内联命名空间更改,因此它基于一组命名空间或符号生成代码,而GCC使用另一组命名空间或符号.在链接时,由于缺少符号而存在问题.
如果我正确地解析双ABI页面,它看起来像是一个转动的问题,_GLIBCXX_USE_CXX11_ABI并abi::cxx11带来一些额外的困难.Red Hat的博客可以在GCC5和C++ 11 ABI以及GCC-5.1案例和两个C++ ABI上阅读更多内容.
下面是一台Ubuntu 15机器.该机器提供GCC 5.2.1.
$ cat test.cxx
#include <string>
std::string foo __attribute__ ((visibility ("default")));
std::string bar __attribute__ ((visibility ("default")));
$ g++ -g3 -O2 -shared test.cxx -o test.so
$ nm test.so | grep _Z3
...
0000201c B _Z3barB5cxx11
00002034 B _Z3fooB5cxx11
$ echo _Z3fooB5cxx11 _Z3barB5cxx11 | c++filt
foo[abi:cxx11] bar[abi:cxx11]
Run Code Online (Sandbox Code Playgroud)
如何使用两个装饰生成带符号的二进制文件(红帽博客称之为"共存")?
或者,我们可以选择哪些选项?
我正试图为用户实现"它只是工作".我不在乎是否存在两个具有两种不同行为的弱符号(std::string缺少写时std::string[abi:cxx11]复制,同时提供写时复制).或者,一个可以是另一个的别名.
Debian在Debian Bug报告日志中 …
对于不修复 C++ 的某些问题的常见解释是,它会破坏 ABI 并需要重新编译,但另一方面,我遇到这样的语句:
老实说,这对于几乎所有 C++ 非 POD 类型都是如此,而不仅仅是例外。可以跨库边界使用 C++ 对象,但通常前提是所有代码都使用相同的工具和标准库进行编译和链接。这就是为什么 MSVC 的所有主要版本都有 boost 二进制文件。
(来自这个SO答案)
那么C++有稳定的ABI吗?
如果是这样,我可以混合和匹配在同一平台上使用不同工具集编译的可执行文件和库(例如 Windows 上的 VC++ 和 GCC)吗?如果没有,有什么办法可以做到吗?
更重要的是,如果 C++ 中没有稳定的 ABI,为什么人们如此担心破坏它?
abi ×10
c++ ×9
arm ×2
32bit-64bit ×1
c ×1
c++11 ×1
clang ×1
elf ×1
g++ ×1
gcc ×1
linux ×1
neon ×1
relocation ×1
return-value ×1
visual-c++ ×1