0%

本文为《C++ 中的内存对齐》系列之下篇,上篇介绍内存对齐的理论基础,建议优先食用~

TL;DR

  • 编译器可能会在结构体中填充字节,以满足所有成员的对齐要求;
  • 可以通过预处理指令 #pragma packalignas 标识符自定义内存对齐;
  • 对于栈上及静态变量,编译器保证遵循其类型的对齐要求;
  • 对于堆上构造的对象,只有在 C++17 后才能保证任何情况下动态申请的内存都满足对齐要求。
Read more »

本文作为上篇主要介绍内存对齐的理论基础,后续的下篇将侧重于 C++ 语言层面的实践,敬请期待!

TL;DR

  • 处理器以若干字节的块而不是单字节的粒度访问内存,因此对于未对齐的内存需要额外的访存及计算开销,导致性能更差。
  • 原子操作和矢量运算指令要求内存地址必须是对齐的,否则可能导致程序死循环和数据错误。
  • 编译器通过 padding 自动对结构中的字段进行对齐,用以向后兼容以及提高效率。
  • 内存对齐在某种意义上扩大了可使用的地址空间范围,甚至影响计算机系统的物理设计。
  • 内存对齐使得处理器能够更好地利用 cache,包括减少 cache line 访问,以及避免多核一致性问题引发的 cache miss。
Read more »

TL;DR

  • 使用无状态的函数对象作为 std::unique_ptr 的删除器不会占用额外的内存空间;而使用函数指针或有状态的函数对象则会增加 std::unique_ptr 对象的大小,其中 std::function 的内存开销最大,应尽量避免使用。
  • MSVC 使用 compressed pair 来存储 std::unique_ptr 的原始指针和删除器,利用 Empty Base Class Optimisation (EBO) 技术来消除空类对象所占用的空间。很多其他厂商也有类似的实现。
  • 在 C++20 中,可以通过 no_unique_address attribute 大幅简化 EBO 的应用。
Read more »

第 10 章 内存

程序的内存布局

  • 栈:用于维护函数调用的上下文
  • 动态链接库映射区:用于映射装载的动态链接库
  • 堆:用来容纳应用程序动态分配的内存区域,一般比栈大很多
  • 可执行文件映像:存储可执行文件在内存里的映像
  • 保留区:对内存中受到保护而禁止访问的内存区域的总称,例如 NULL
Read more »

第 6 章 可执行文件的装载与进程

进程虚拟地址空间

  • 程序被运行起来后将拥有自己独立的虚拟地址空间(Virtual Address Space),其大小由 CPU 的位数决定
  • 进程只能使用那些操作系统分配给进程的地址,访问未经允许的空间是非法的,将会强制结束进程
  • Linux 下,4GB 虚拟空间被划分成两部分,操作系统使用 1GB(0xC00000000~0xFFFFFFFF),进程(原则上)使用 3GB(0x00000000~0xBFFFFFFF)
  • Windows 下,操作系统占用 2GB,进程使用 2GB(可以在根目录下的 Boot.ini 中修改参数为 3G)
  • Intel 通过 PAE(Physical Address Extension)将 32 位地址线扩展为 36 位,并修改了页映射的方式,从而可以访问到更多的物理内存
  • 操作系统提供一个窗口映射的方法,把这些额外的内存映射到进程地址空间中来,应用程序可以根据需要来选择申请和映射,这在 Windows 下叫做 AWE(Address Windowing Extensions),而像 Linux 等 UNIX 类操作系统则采用 mmap() 系统调用来实现
Read more »

第 2 章 编译和链接

被隐藏了的过程

  • 使用 GCC 编译 Hello World 程序 gcc hello.c,可以分解为 4 个步骤,分别是预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)

    Read more »

第 1 章 温故而知新

Hello World

  • 程序为什么要被编译器编译了之后才可以运行?
  • 编译器在把 C 语宫程序转换成可以执行的机器码的过程中做了什么,怎么做的?
  • 最后编译出来的可执行文件里面是什么?除了机器码还有什么?它们怎么存放的,怎么组织的?
  • #include <stdio.h> 是什么意思?把 stdio.h 包含进来意味着什么?C语言库又是什么?它怎么实现的?
  • 不同的编译器(Microsoft VC、GCC)和不同的硬件平台(x86、 SPARC、 MIPS、 ARM),以及不同的操作系统(Windows、 Linux、 UNIX、 Solaris),最终编译出来的结果一样吗?为什么?
  • Hello World 程序是怎么运行起来的?操作系统是怎么裝载它的?它从哪儿开始执行,到哪儿结束?main 函数之前发生了什么?main 函数结束以后又发生了什么?
  • 如果没有操作系统,Hello World 可以运行吗?如果要在一台没有操作系统的机器上运行 Hello Worid 需要什么?应该怎么实现?
  • printf 是怎么实现的?它为什么可以有不定数最的参数?为什么它能够在终端上输出字符串?
  • Hello World 程序在运行时,它在内存中是什么样子的?
Read more »

Only you know enough about the software you’re writing, the environment in which it will run, and the context in which it’s being created to determine whether it’s reasonable to violate the guidelines I present. Most of the time, it won’t be, and the discussions that accompany each Item explain why. In a few cases, it will. Slavish devotion to the guidelines isn’t appropriate, but neither is cavalier disregard. Before venturing off on your own, you should make sure you have a good reason.

Read more »

The Items in this book are guidelines, not rules, because guidelines have exceptions. The most important part of each Item is not the advice it offers, but the rationale behind the advice. Once you’ve read that, you’ll be in a position to determine whether the circumstances of your project justify a violation of the Item’s guidance. The true goal of this book isn’t to tell you what to do or what to avoid doing, but to convey a deeper understanding of how things work in C++11 and C++14.

Read more »