【问题标题】:Is pointer arithmetic allowed for malloc'ed blocks?malloc'ed 块是否允许指针算术?
【发布时间】:2023-03-04 19:31:01
【问题描述】:

我正在阅读 C 标准中的以下内容:

(6.5.6 加法运算符)

9 当两个指针相减时,都应该指向 相同的数组对象,或超过数组的最后一个元素 目的;结果是两者下标的差 数组元素。

现在我想知道什么被认为是“数组对象”。更具体地说,我想知道以下愚蠢的例子是否合法?分配的内存块是否被视为一个“数组对象”?

uint8_t *data = malloc(255);
uint8_t *end = data + 255;
ptrdiff_t size = end - data;

【问题讨论】:

  • 毫无疑问,就指针运算和§6.5.6 而言,malloc 的内存块绝对是一个“数组对象”。 (不过,我不能引用章节。)
  • @SteveSummit 谢谢史蒂夫,这些话让我平静了一点——我在很多地方都使用过这种指针算法

标签: c pointers language-lawyer pointer-arithmetic


【解决方案1】:

我在标准中找不到任何内容来准确定义“数组对象”的组成部分,但查看 C11 标准草案 7.22.3 中的内存分配函数,我确实发现了这一点:

连续调用分配的存储的顺序和连续性 aligned_alloccallocmallocrealloc 功能未指定。这 如果分配成功,则返回的指针经过适当对齐,以便可以分配给 指向具有基本对齐要求的任何类型对象的指针,然后 使用 访问分配的空间中的此类对象或此类对象数组(直到空间 被显式释放)。

它并不像人们想要的那样明确,但它确实表明从这些函数返回的内存可以用作数组,因此应该适用指针运算规则。

【讨论】:

  • 阅读标准时,我无法真正理解静止对象是否真的 具有 类型(但在标准允许的情况下,仍然可以使用某些特定其他类型的左值访问) ,或者如果他们“拥有”的唯一类型是用于访问它们的类型。令人困惑。
【解决方案2】:

出于非语言律师的目的,是的。

出于语言律师的目的,我认为 uint8_t 不能保证算术运算,但它是字符类型(charunsigned charsigned char)。

根据 C 2018 7.22.3.4 2 和 3,如果 malloc 不返回空指针,则返回值指向为请求大小的对象分配的空间。根据 3.15 1,对象是“执行环境中的数据存储区域,其内容可以表示值”。 malloc提供的空间是执行环境中的数据存储区域,其内容可以表示值,即使它们还没有。

如果我们将malloc 的结果分配给指向字符类型的指针,6.3.2.3 7 将适用:“……当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续增量,直到对象的大小,产生指向对象剩余字节的指针。”虽然没有明确说明,但这被理解为可以将对象视为字符类型的数组,这是 C 标准的其他部分所要求的,例如 6.5 6(“如果将值复制到没有声明类型……作为字符类型的数组,……)。

因此,指针算术运算是针对此对象上的char *unsigned char *signed char * 类型的指针定义的。

uint8_t,如果它由<stdint.h> 定义,则必须具有与unsigned char 大致相同的属性(两者都是纯二进制,uint8_t 不能大于unsigned char,因为unsigned char 必须支持值 255,并且uint8_t 不能小于unsigned char,因为根据定义,字符类型是对象大小的基本单位),不需要相同的类型。它可以是扩展的整数类型,如 6.2.5 4 所允许的那样,因此可能不在关于将指针转换为字符类型的规则中。

【讨论】:

  • 按照您的逻辑,int *ip = malloc(255 * sizeof(int)); 会是指向数组的指针吗? (或uint32_t。)如果我使用malloc 为一些结构类型的对象分配存储空间,它还会被视为数组吗?
  • @1201ProgramAlarm:使用int *,情况就不太清楚了。该标准特别对待字符指针,允许我们将分配的对象(存储区域)视为字节数组,即使尚未写入任何内容。但是,对于其他类型,它有关于“有效类型”的规范,解释起来很麻烦。出于实际目的,此指针算术有效。出于语言律师的目的,其他人可以回答,否则我将不得不再次研究。
  • @EricPostpischil 谢谢!如果我正确理解您的答案,那么确定使用 unsigned char 会更安全吗?您个人认为在现有项目中将 uint8_t 类型更改为无符号字符是否值得?
  • 这完全取决于您需要保证多少可移植性。 “出于非语言律师的目的,是的。” 总结了这一点。在我能想到的每个当前系统上,都没有必要从 uint8_t 更改为 unsigned char —— 更改将纯粹是语义上的。但是,如果您必须保证某些深奥的假设机器符合最严格的标准,那么请考虑更改。 (@EricPostpischil - 另一个很好的答案...)
【解决方案3】:

我认为标准中的相关引用来自第 7.22.3 节,“内存管理功能”,第 1 段:

如果分配成功,则返回的指针经过适当对齐,以便可以分配给 指向具有基本对齐要求的任何类型对象的指针,然后使用 在分配的空间中访问这样的对象或这样的对象的数组(直到空间 被显式释放)。

(强调我的。)

所以malloc返回的内存是一个数组,你自己计算指针差是合法的。

【讨论】:

    【解决方案4】:

    按照标准:

    当两个指针相减时,两个指针都指向 相同的数组对象,或数组对象的最后一个元素。

    [C17 § 6.5.6]

    否则行为未定义。标准很明确。由于malloc 返回的值可以分配给指向“任何类型的对象”的指针,然后用于访问“此类对象的数组”[C17 § 7.22.3],因此适用上述规则。

    【讨论】:

    • 虽然 C17 引用很好,但目前没有 html 形式的副本。如果文本没有改变,最好引用C11 Standard - latest draft(它还提供标准中每个段落的链接)例如C11 Standard - 7.22.3 Memory management functions
    • 依靠落后于当前标准 8 年的草案并不是一个好主意。
    • 并不是说它是最好的选择,但是引用提问者没有的东西(除非你包括文本)也不是一个很好的选择。在许多世界中(在 VS2017 之前),任何比 C89 更新的东西都是触手可及的。
    • OP 询问的是该语言的当前版本,不是已被取代 9 次的过去版本。
    • 没关系。这只是一个建议。这不是作为一个叮当声。另一种方法是在 .pdf 中引用 C17 标准的最新草案(需要完整下载,并且无法提供对相关特定段落的跳转引用)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-08
    • 2020-04-12
    • 2016-12-15
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多