这是char数组可以与任何数据类型一起使用吗?
我知道动态内存和malloc的常见实现,可以在维基百科上找到参考资料。我还知道malloc返回的指针可以转换为程序员想要的任何指针,甚至没有警告,因为标准在6.3.2.3 Pointers§1 中有规定
指向void的指针可以转换为指向任何不完整对象的指针类型指向任何不完整或对象类型的指针都可以转换为指向void的指针然后再次返回;结果应与原始指针进行比较。
问题是,假设我有一个没有malloc
和free
的独立环境,我如何在符合要求的C中构建这两个函数的实现?
如果我在标准方面有一些自由,这很容易:
- 从一个大字符数组开始
- 使用相当大的对齐(对于许多体系结构来说,8应该足够了)
- 实现一个算法,以该对齐方式从该数组返回地址,并跟踪已分配的内容——在malloc实现中可以找到很好的例子
问题是,该实现返回的指针的有效类型仍然是char *
标准在同一段§7
指向对象或不完整类型的指针可以转换为指向不同类型的指针对象或不完整的类型。如果结果指针没有正确对齐指向类型,行为未定义。否则,当再次转换回时结果应与原始指针进行比较。
这似乎不允许我假装被声明为简单字符的东西可以神奇地包含另一种类型,甚至在这个数组的不同部分或同一部分的不同时刻包含不同的类型。用不同的方式去引用这样的指针似乎是对标准的严格解释的未定义行为。这就是为什么当您在字符串缓冲区中获得对象的字节表示时,例如当您从网络流中读取对象时,常见的习惯用法使用memcpy
而不是别名。
那么,我如何在纯C中构建malloc的一致实现呢???
这个答案只是对标准的解释,因为我在C99 n1256草案和C11 n1570中都找不到明确的答案。
基本原理来自C++标准(C++14草案n4296)。3.8物体寿命〔basic.life〕说(强调我的):
§1当出现以下情况时,T型物体的寿命开始:
- 获得了具有适当排列和尺寸的T型存储,并且
- 如果对象具有非真空初始化,则其初始化完成
T类型对象的寿命在以下情况下结束:
- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用启动,或者
- 对象占用的存储被重用或释放
和
§3本国际标准中赋予物体的特性仅适用于给定物体在其使用寿命内。
我知道C和C++是不同的语言,但它们是相关的,以上仅用于解释以下解释
C标准中的相关部分是7.20.3内存管理功能。
如果分配success是适当对齐的,因此它可以分配给指向任何类型对象的指针然后用于访问分配的空间中的这样的对象或这样的对象的阵列(直到明确释放空间为止)已分配对象的生存期延长从分配到解除分配。每个此类分配应产生一个指针,指向与任何其他对象不相交的对象指针返回指向起始位置(最低字节地址)。。。
我的解释是,如果你有一个大小和对齐正确的内存区域,例如一个大字符数组的一部分,但这里可以使用任何其他类型的数组,你可以假设它是一个指向未初始化对象或另一种类型(比如T)的数组的指针,并将该区域第一个字节的char或void指针转换为新类型(T)的指针。但为了不违反严格的别名规则,该区域不能再通过任何以前的值、指针或初始类型访问-如果初始类型是字符,则仍允许读取,但写入可能会导致陷阱表示。由于此对象未初始化,因此它可能包含陷阱表示,并且在初始化之前读取它是未定义的行为。这个T对象及其关联的指针将一直有效,直到您决定将内存区域用于任何其他用途,并且指向T的指针在那时变为悬空。
TL/DR:严格的别名规则只要求内存区域在一个时刻只能包含一个有效类型的对象。但是您可以为提供的不同类型的对象重新使用内存区域:
- 尺寸和对齐方式兼容
- 在使用新对象之前,使用正确的值对其进行初始化
- 您不再访问初始对象
因为通过这种方式,您只需将内存区域用作已分配的内存。
根据C标准,初始对象的生存期不会结束(静态对象持续到程序结束,自动对象持续到其声明范围结束),但由于严格的别名规则
C标准的作者在指定显然不是所希望的行为方面付出了比那些行为多得多的努力,因为他们希望明智的编译器编写者能够支持有用的行为,无论标准是否强制执行,由于钝编译器,编写者可以生成完全兼容但完全无用的"兼容"实现(*)。
在C89出现之前,在许多平台上都可以编写可靠高效的malloc()等价物,我认为没有理由相信作者的意图是,为一个以前能够处理malloc)等价物的平台编写C89编译器的人不会让这些实现像他们的前代一样有能力。不幸的是,在20世纪90年代流行的语言(它是C89及其前身的组合超集)已经被一种质量较差的方言所取代,这种方言省略了C89作者认为理所当然的特征,并期望其他人也这样做。
甚至除了如何获得记忆的问题之外,更大的问题是malloc()承诺新分配的内存在最坏的情况下会保持不确定性价值;由于结构类型没有陷阱表示,使用结构类型的指针读取此类存储将定义行为如果存储器先前是使用某种其它类型写入的,但是,读取的结构类型将具有"未定义的行为",除非free()或malloc()在物理上擦除所有有问题的存储,因此否定了malloc()的性能优势,而不仅仅是calloc()。
(*)如果存在至少一组源文件,实现在没有UB的情况下以兼容的方式处理,则当给定任何其他源文件集时,一个实现可能需要任意(可能不可能大)的堆栈空间,并且如果该空间不可用,则以任意的方式执行。