需要帮助理解C++;程序 我不太理解C++程序的编译和链接。有一种方法,我可以查看编译C++程序(以可理解的格式)生成的对象文件。这将帮助我理解对象文件的格式,编译C++类,需要编译器生成对象文件所需的信息,并帮助我理解如下语句:

需要帮助理解C++;程序 我不太理解C++程序的编译和链接。有一种方法,我可以查看编译C++程序(以可理解的格式)生成的对象文件。这将帮助我理解对象文件的格式,编译C++类,需要编译器生成对象文件所需的信息,并帮助我理解如下语句:,c++,linker,compilation,object-files,C++,Linker,Compilation,Object Files,如果类仅用作输入参数和返回类型,则不需要包含整个类头文件。正向声明就足够了,但如果派生类派生自基类,则需要包含包含基类定义的文件(取自“Exception C++”) 我正在阅读“链接和加载”这本书来理解对象文件的格式,但是我更喜欢专门为C++源代码定制的东西。 谢谢 贾格拉蒂 编辑: 我知道使用nm可以查看对象文件中存在的符号,但我有兴趣了解更多有关对象文件的信息。您是否尝试过使用readelf检查二进制文件(如果您在Linux平台上)?这提供了关于ELF对象文件的非常全面的信息 不过,老实说

如果类仅用作输入参数和返回类型,则不需要包含整个类头文件。正向声明就足够了,但如果派生类派生自基类,则需要包含包含基类定义的文件(取自“Exception C++”)

我正在阅读“链接和加载”这本书来理解对象文件的格式,但是我更喜欢专门为C++源代码定制的东西。 谢谢

贾格拉蒂

编辑:


我知道使用nm可以查看对象文件中存在的符号,但我有兴趣了解更多有关对象文件的信息。

您是否尝试过使用
readelf
检查二进制文件(如果您在Linux平台上)?这提供了关于ELF对象文件的非常全面的信息


不过,老实说,我不确定这对理解编译和链接有多大帮助。我认为正确的方法可能是要了解C++代码是如何映射到装配前和后链接的。

< P>,通常不需要详细了解Objo文件的内部格式,因为它们是为您生成的。您需要知道的是,对于您创建的每个类,编译器都会生成一个Obj文件,它是您的类的二进制字节码,适用于您编译的操作系统。然后,下一步-链接-将把程序所需的所有类的对象文件放在一个EXE或DLL(或非Windows OS es的任何其他格式)中。也可以是EXE+多个DLL,具体取决于您的意愿

最重要的是将类的接口(声明)和实现(定义)分开

始终仅将类的头文件接口声明放入其中。没有其他内容-这里没有实现。还要避免使用自定义类型的成员变量,因为对于它们来说,转发声明是不够的,需要在头中包含其他头。如果标题中包含了include,则设计会发出异味,并且会减慢构建过程

类方法或其他函数的所有实现都应该在CPP文件中。这将保证当有人包含您的头文件时,不需要编译器生成的Obj文件,而您只能在CPP文件中包含其他人的头文件

但是为什么要麻烦呢?答案是,如果你有这样的分离,那么链接会更快,因为你的每个Obj文件在每个类中使用一次。此外,如果您更改了类,那么在下一次构建期间,也会更改少量其他对象文件


如果您在头文件中包含了include,这意味着当编译器为您的类生成Obj文件时,它应该首先为头文件中包含的其他类生成Obj文件,这可能还需要其他Obj文件等等。甚至可能是一个循环依赖,然后你不能编译!或者,如果您更改了类中的某些内容,那么编译器将需要重新生成许多其他Obj文件,因为如果您不分离,它们在一段时间后会变得非常紧密相关。

nm
是一个unix工具,它将向您显示对象文件中符号的名称

objdump
是一个GNU工具,它将向您显示更多信息


但这两种工具都会向您显示链接器使用的、但不是为人类阅读而设计的原始信息。这可能不会帮助你更好地理解C++级别的发生。反汇编编译器输出很可能不会以任何方式帮助您理解您遇到的任何问题。编译器的输出不再是C++程序,而是普通的汇编,如果你不知道内存模型是什么,那就真的很难阅读。 关于将
base
声明为
derived
的基类时为什么需要定义
base
这一特定问题,有几个不同的原因(我可能忘记了更多原因):

  • 当创建类型为
    derived
    的对象时,编译器必须为完整实例和所有子类保留内存:它必须知道
    base
  • 访问成员属性时,编译器必须知道与隐式
    this
    指针的偏移量,该偏移量需要知道
    子对象的大小
  • 当在
    派生的
    上下文中解析标识符,并且在
    派生的
    类中找不到该标识符时,编译器必须知道它是否在
    中定义,然后才能在封闭的名称空间中查找该标识符。编译器无法知道
    foo()base
    类中声明了
    foo()
    ,则code>是
    derived::function()
    内部的有效调用
  • 当编译器定义
    派生类时,必须知道
    base
    中定义的所有虚拟函数的数目和签名。它需要这些信息来构建动态调度机制(通常是vtable),甚至需要知道
    派生的
    中的成员函数是否绑定为动态调度——如果
    base::f()
    是虚拟的,那么
    派生的::f()就是虚拟的
    将是虚拟的,无论
    derived
    中的声明是否具有
    virtual
    关键字
  • 多重继承增加了一些其他要求——比如每个
    baseX的相对偏移量
    
    void f() {
       derived d;
       //...
    }