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

时间:2016-07-21 22:10:51

标签: c malloc language-lawyer

这是Can a char array be used with any data type?

的后续内容

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

  

指向void的指针可以转换为指向任何不完整或对象的指针   类型。指向任何不完整或对象类型的指针可以转换为指向void的指针   又回来了;结果应该等于原始指针。

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

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

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

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

标准在同一段§7中说明

  

指向对象或不完整类型的指针可能会转换为指向其他对象的指针   对象或不完整的类型。如果生成的指针未正确对齐   指向类型,行为未定义。否则,当再次转换回来时,   结果应与原始指针进行比较。

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

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

2 个答案:

答案 0 :(得分:3)

这个答案只是该标准的解释,因为我无法在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内存管理功能。

  

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

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

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

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

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

每个C标准,初始对象的生命周期不会结束(静态对象一直持续到程序结束,自动对象直到它们的声明范围结束),但是你不能再访问它了严格别名规则

答案 1 :(得分:1)

C标准的作者在指定 标准规定了它,因为钝的编译器编写者可以生产符合标准的"完全兼容但完全无用的实现(*)。

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

即使超越了如何获得记忆的问题,更大的问题就是如此 malloc()承诺新分配的内存最坏的情况下会保留 不确定的价值;因为结构类型没有陷阱表示, 使用结构类型的指针读取这样的存储将被定义 行为。如果以前使用其他类型写入内存, 但是,除非两者之一,否则结构类型读取将具有未定义的行为 free()或malloc()以物理方式擦除所有存储空间, 因此否定了拥有malloc()的性能优势而不仅仅是 释放calloc()。

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