我知道 C 和 C++ 程序之间的源代码存在差异——这不是我要问的。
我也知道这会因 CPU 和操作系统而异,具体取决于编译器。
我正在自学 C++,并且我看到了许多对两种语言都可以使用的库的引用。这让我开始思考——两种语言的二进制可执行文件之间是否存在显着差异?
对于两者都可以轻松使用的库,我认为它们必须在可执行级别上相似。
在很多情况下,人们可以检查可执行文件并判断它是由 C 还是 C++ 源代码创建的?或者二进制文件会非常相似吗?
我知道 C 和 C++ 程序之间的源代码存在差异——这不是我要问的。
我也知道这会因 CPU 和操作系统而异,具体取决于编译器。
我正在自学 C++,并且我看到了许多对两种语言都可以使用的库的引用。这让我开始思考——两种语言的二进制可执行文件之间是否存在显着差异?
对于两者都可以轻松使用的库,我认为它们必须在可执行级别上相似。
在很多情况下,人们可以检查可执行文件并判断它是由 C 还是 C++ 源代码创建的?或者二进制文件会非常相似吗?
在大多数情况下,是的,这很容易。以下是我经常看到的一些线索,很容易记住它们:
this
指针传递给 C++ 成员函数。同样,由于this
指针在 C 中根本不存在,因此您很少会看到直接模拟(尽管在某些情况下,它们会使用相同的约定来传递其他指针,因此您需要小心这个)。可执行文件就是可执行文件,无论它是用什么语言编写的。如果它是为目标体系结构构建的,它将在该体系结构上运行。
C 和 C++ 编译代码之间的(可以说)最重要的区别,以及与可以链接到 C 和 C++ 可执行文件的库相关的一个,是名称修改。 基本上:当一个库被编译时,它会导出一组符号(函数名、导出的变量等),链接到该库的可执行文件可以使用这些符号。这些符号是如何命名的是相当特定于编译器/链接器的,如果后续的可执行文件使用不兼容约定的链接器链接,则符号将无法正确解析。此外,C 和 C++ 的约定略有不同。上面链接的维基百科文章有更多细节;可以说,在头文件中声明导出符号时,您通常会看到如下结构:
#ifdef __cplusplus
extern "C" {
#endif
/* exported declarations here */
#ifdef __cplusplus
}
#endif
__cplusplus
是仅在编译 C++ 代码时定义的预处理器宏。这里的想法是,当在 C++ 中使用头文件时,编译器被指示使用 C 命名导出符号的方式(在 " extern "C" { /* foo */ }
" 块内,因此库可以在 C 和 C++ 中正确链接。
我想我可以通过读取反汇编的二进制代码来判断是 C++ 还是 C [对于我熟悉的处理器架构,x86、x86_64 和 ARM]。但实际上,并没有太大区别,您必须看起来很难确定。
要寻找的标志是“间接调用”( function pointer calls via a table
) 和this
-pointers。尽管 C 可以有pointer to struct
参数并且经常使用函数指针,但它通常不是以 C++ 的方式设置的。此外,有时您会注意到,编译器采用指向结构的指针并添加一个小的偏移量——这将删除继承类的外层。这也可以在 C 中发生,但不会那么普遍/独特。
只看二进制文件[除非你可以“在你的头脑中进行拆卸”会更难——尤其是如果它被剥离了符号——这就像那个可以告诉你旧黑胶唱片上的古典音乐是什么的人在轨道上[隐藏标签] - 大多数人都无法做到,即使他们“很好”。
在实践中,C 程序(或 C++ 程序)很少只是纯标准 C(或 C++)(例如 C99 标准没有扫描目录的意思)。所以程序使用额外的库。
在 Linux 上,大多数二进制文件都是动态链接的。使用ldd
命令查找。
如果二进制文件链接到stdc++
库,则源代码可能是 C++。
如果仅libc.so
链接库,则源代码可能只有 C(但您可以静态链接libstdc++.a
库)。
您还可以使用处理二进制文件的工具(例如Linux 上的objdump
, readelf
, strings
, nm
....)来查找有关它们的更多信息。
C 和 C++ 编译器生成的代码通常是相同的代码。有两个重要的区别:
您可以使用这样的块来让 C++ 编译器生成与 C 兼容的代码:
extern "C" {
/* code */
}