是否有可能在C中编写一致的malloc实现?

这是一个char数组可以与任何数据类型一起使用的后续内容吗?

我知道动态内存和malloc的常见实现,可以在维基百科上找到引用。 我也知道malloc返回的指针可以转换成程序员想要的任何东西,甚至没有警告,因为6.3.2.3指针中的标准状态§1

指向void的指针可以转换为指向任何不完整或对象类型的指针。 指向任何不完整或对象类型的指针可能会转换为指向void的指针并再次返回; 结果应该等于原始指针。

问题是假设我有一个没有mallocfree的独立环境,我如何在符合C的情况下构建这两个函数的实现?

如果我对标准采取一些自由,很容易:

  • 从一个大字符数组开始
  • 使用相当大的对齐(对于许多架构,8应该足够)
  • 实现一个算法,从该数组返回地址,在该对齐,跟踪已分配的内容 – 在malloc实现中可以找到很好的例子?

问题是该实现返回的指针的有效类型仍然是char *

标准在同一段§7中说明

指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。 如果结果指针未针对指向类型正确对齐,则行为未定义。 否则,当再次转换回来时,结果应该等于原始指针。

这似乎并不允许我假装被声明为简单字符的内容可以神奇地包含另一种类型,甚至是该数组的不同部分中的不同类型或同一部分中的不同时刻。 不同地解释引用这样的指针似乎是未定义的行为,并严格解释标准。 这就是为什么当你在字符串缓冲区中获得对象的字节表示时,常见的习语使用memcpy而不是别名,例如当你从网络流中读取它时。

那么如何在纯C中构建一个符合malloc的实现呢?

C标准的作者在指定那些明显不可取的行为方面付出了更多的努力,因为他们期望合理的编译器编写者支持有用的行为,无论标准是否强制执行,并且因为编写器的编写者会产生钝的完全兼容但完全没用的“兼容”实现(*)。

在C89出现之前,可以在许多平台上编写可靠且高效的malloc()等价物,我认为没有理由相信作者希望人们为一个能够处理malloc()的平台编写C89编译器以前的等价物不会使这些实现与它们的前辈一样有能力。 不幸的是,在20世纪90年代流行的语言(它是C89及其前身的组合超集)已被一种质量低劣的方言所取代,这种方言省略了C89作者本可以理所当然的特征,并期望其他人也这样做。 。

甚至超出了如何获取内存的问题,更大的问题是malloc()承诺新分配的内存最坏的情况下会保留不确定值; 因为结构类型没有陷阱表示,所以使用结构类型的指针读取这样的存储将具有已定义的行为。 但是,如果内存先前是使用其他类型编写的,则结构类型读取将具有未定义的行为,除非free()或malloc()物理擦除所有相关存储,从而否定了malloc的性能优势( )而不仅仅是calloc()。

(*)如果存在至少一组源文件,实现以兼容的方式处理而没有UB,则在给定任何其他源文件集时,实现可能需要任意(可能是不可能的大)的堆栈空间量,并且表现为任意时尚,如果该空间不可用。

这个答案只是对标准的解释 ,因为我无法在C99 n1256草案或C11 n1570中找到明确的答案。

理由来自C ++标准(C ++ 14 draft n4296)。 3.8对象寿命[basic.life]说(强调我的):

§1类型T对象的生命周期始于:

  • 获得具有适当对齐和T型尺寸的存储 ,并且
  • 如果对象具有非空的初始化,则其初始化完成。

类型T的对象的生命周期在以下情况结束:

  • 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
  • 对象占用的存储器被重用或释放。

§3本国际标准中归属于对象的属性适用于给定对象的生命周期

我知道C和C ++是不同的语言,但它们是相关的,上面仅用于解释以下解释

C标准的相关部分是7.20.3内存管理function。

…如果分配成功,则返回的指针被适当地对齐,以便可以将其指定给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空间为明确解除分配)。 分配对象的生命周期从分配延伸到解除分配 。 每个这样的分配都应该产生一个指向与任何其他对象不相交的对象的指针。 返回的指针指向分配空间的开始(最低字节地址)

我的解释是,如果你有一个具有正确大小和对齐的内存区域,例如大字符数组的一部分,但是在这里可以使用任何其他类型的类型数组,你可以假装它是指向未初始化对象的指针或者是另一种类型的数组(比如说T),并将char或void指针转换为区域的第一个字节,转换为新类型(T)的指针。 但是为了不违反严格的别名规则,必须不再通过任何先前的值或指针或初始类型访问此区域 – 如果初始类型是字符,则仍然允许读取,但写入可能导致陷阱表示。 由于此对象未初始化,因此它可以包含陷阱表示并在其初始化为未定义行为之前读取它。 此T对象及其关联的指针将一直有效,直到您决定将内存区域用于任何其他用法,并且指向T的指针在此时变为悬空。

TL / DR:严格别名规则仅强制要求内存区域在一个时刻只能包含一个有效类型的对象。 但是,您可以将内存区域重新用于提供的其他类型的对象:

  • 大小和对齐是兼容的
  • 在使用之前使用正确的值初始化新对象
  • 您不再访问初始对象

因为这样您只需将内存区域用作已分配的内存。

按照C标准,初始对象的生命周期不会结束(静态对象会一直持续到程序结束,自动对象直到它们声明范围结束),但由于严格的别名规则,您无法再访问它