如何在 C 中以可移植的方式管理内存对齐和通用指针算法?

Posted

技术标签:

【中文标题】如何在 C 中以可移植的方式管理内存对齐和通用指针算法?【英文标题】:How to manage memory alignments and generic pointer arithmetics in a portable way in C? 【发布时间】:2012-01-23 08:47:55 【问题描述】:

我必须实现 malloc/realloc/free 的优化版本(为我的特定应用程序量身定制)。目前代码在特定平台上运行,但如果可能的话,我想以可移植的方式编写它(平台将来可能会改变),或者至少我想将可能的平台差异集中在一个单一的点(可能是 .h)。我知道一些问题:

内存对齐的差异 适合“通用”分配的最小内存块大小的差异 指针大小的差异

(这里我将忽略用于内存分配的基本系统服务的差异,因为在某些嵌入式系统上它们可能根本不可用。假设我们在一个大的预分配内存块上工作以用作“堆” )。

问题:

C 中是否有用于这种目的的标准宏或函数? 在这份工作中我还可能面临哪些其他问题?

【问题讨论】:

【参考方案1】:

确保保持适合所有基本类型的对齐方式的经典方法是定义一个联合:

union alloc_align 
    void *dummy1;
    long long dummy2;
    long double dummy3;
;

...然后确保您分发的地址总是与您从系统内存分配器收到的对齐地址偏移sizeof (union alloc_align) 的倍数。

我相信在 K&R 中描述了与此类似的方法。

【讨论】:

老式的,但它可能工作。我会添加“intptr_t”和一个指向函数的指针。谢谢【参考方案2】:

对齐功能仅在新的 C 标准 C11 中处理。它有关键字_Alignof_Alignas 和一个函数aligned_alloc。使用大多数现代编译器来模拟这些功能并不难(如其他答案所示),因此我建议您根据__STDC_VERSION__ 编写自己使用的小型宏或包装器。

【讨论】:

谢谢!使用 _Alignof 和朋友(如果 SDK 不这样做,则在特定于平台的文件中实现它们)是更干净的方法。 OP 要求提供可移植代码,而您的答案是 C11?据我所知,目前还没有任何编译器支持这个。事实上,很多编译器甚至不支持 C99。使用特定于 C11 的特性可能是使代码在未来 5-10 年左右不可移植的某种方式。 @Lundin,我不是说他应该使用它,我说他应该模仿它,这些是要使用的接口。对 C11 的支持将比对 C99 更快。它基本上添加了已经存在的功能(例如,对齐),或者使其他功能成为可选的(例如 VLA、线程或原子)。 @Jens Gustedt:没错,我就是这么想的。 @Lundin:我刚刚与 Shane McLaughlin 更深入地讨论了这个问题,见下文。谢谢大家!【参考方案3】:

不幸的是,对齐内存因编译器而异(这是一个问题),在 MSVC 上,您有 aligned_malloc,对于 Linux,您还有 POSIX memalign,然后还有在 ICC 下工作的 _mm_alloc, MSVC和GCC,IIRC,应该是最便携的。

第二个问题是对齐时的内存浪费,这不是主要问题,但在嵌入式系统上,需要注意。

如果您正在堆栈分配需要对齐的东西(例如 SIMD 类型),您还需要查看 __attribute__((__aligned__(x)))__declspec(align(x))

就指针算术的可移植性而言,您可以使用 stdint.h/pstdint.h 中的类型来执行此操作,但在 uintptr_t 和指针之间进行转换时,标准可能会说明 UB(不幸的是,标准不是我的强项:()。

【讨论】:

谢谢。不幸的是,这类问题的标准似乎很薄弱:( ...【参考方案4】:

主要问题是你只提供了内存块的总大小给malloc()和朋友,没有任何关于对象粒度的信息。如果您将分配视为对象数组,那么您的大小是基本对象的 sizeof,而数字 n 是数组中的对象数,例如:

p = malloc(sizeof(*p) * n);

如果您只有总大小,那么您不知道是 s=4 和 n=10,还是 s=2 和 n=20,或者 s=1 和 n=40,因为所有乘以总大小为 40 字节。

所以基本问题是,您是否想要直接替代原始功能,例如当您在整个代码库中抛出本地调用时,或者您是否具有带有包装函数的集中式和 DRY 模块化。在那里你可以使用提供 s 和 n 的函数。

void *my_malloc (size_t s, size_t n)

大多数情况下,当返回的绝对内存地址是 s 的倍数以保证正确对齐时,应该是安全的选择。

或者,在移植您的实现时,您只需查看本机 malloc() 用于目标平台的对齐方式(例如 16 的倍数),并将其用于您自己的实现。

【讨论】:

不幸的是,由于许多原因,我需要一个“普通”的 malloc 替代品。你是完全正确的:如果我可以对要分配的对象做出一些假设,我的工作会更简单。但是要接触的代码太多了。【参考方案5】:

如果您查看#pragma pack,这可能会对您有所帮助,因为它允许您定义结构打包并在大多数编译器上实现。

【讨论】:

但是... malloc 生成的指针必须保留平台的对齐和打包约束特性。所以我应该见面而不是修改它们。 “#pragma pack”可能会帮助我开发核心分配引擎,你是对的。但我的问题更多是关于如何以尽可能标准的方式了解(并实现)系统约束。再见! #pragma pack 绝对是完全不可移植的。 @JanHudec:它适用于 MSVC、GCC、ICC、WATCOM 和其他一些工具,对我来说似乎很便携(但它可能没有标准化)。 @Giuseppe Guerrini:标准与便携不同。您接受的答案符合新兴标准,但这些标准尚未在许多平台上实施,因此不是特别便携。例如,查看对 Windows CE 或移动设备的编译器支持。为了获得最大的可移植性,您需要多年来一直是普通代码的代码,而不是该语言的最新和最伟大的新版本。 一般来说,你是对的。我的目标是生成一个“编写良好”的核心引擎,其中应用了所有“最佳实践”(_Alignof ...),以及一个尽可能小的“系统相关”层,其中每个 SDK 的泄漏都已修复。理想情况下,在(可能很远的 :-( ) 未来,所有 SDK 都应该实现“最佳协议”,而“系统相关”部分应该消失。这是我的希望。我是梦想家吗?:D【参考方案6】:

C 说malloc 返回一个指向任何目的对齐的内存的指针。 C 中没有可移植的方式来使用 C 功能实现这一点。这导致malloc 是一个函数,如果用 C 编写,就不能以可移植的方式编写。

(C99, 7.20.3p1) "如果分配成功,返回的指针经过适当对齐,以便可以将其分配给指向任何类型对象的指针,然后用于访问此类对象或此类对象的数组分配的空间(直到空间被显式释放)。”

【讨论】:

使其可移植:如何将指针转换为 (char*) 并添加正确的偏移量。那将被定义。

以上是关于如何在 C 中以可移植的方式管理内存对齐和通用指针算法?的主要内容,如果未能解决你的问题,请参考以下文章

以可移植的方式使用 DB Api

如何以可移植的方式创建流程?

在 MySQL 和 Python 中以可重复的方式处理 1970 年之前的日期

Visual Studio2008 C++结构体成员需要内存对齐吗?

是否有一种可移植的(C++ 标准)方法来计算前一个对齐的指针?

如何在C中对齐指针