malloc分配的内存块可以进行指针算术运算吗?

6
我正在阅读C语言标准中的以下内容:
(6.5.6 加法运算符) 9 当两个指针相减时,它们都必须指向同一数组对象的元素,或者是数组对象的最后一个元素之后;结果是两个数组元素的下标之差。
现在我想知道什么被认为是“数组对象”。 更具体地说,我想知道下面这个愚蠢的例子是否合法? 那个分配的内存块是否被认为是一个“数组对象”?
uint8_t *data = malloc(255);
uint8_t *end = data + 255;
ptrdiff_t size = end - data;

3
毫无疑问,就指针算术和§6.5.6所涉及的内容而言,通过malloc分配的内存块肯定是一个单一的“数组对象”。(虽然我无法引用具体的章节和段落。) - Steve Summit
@SteveSummit 谢谢 Steve,你的话让我稍微冷静了一些——我在很多地方都使用了这种指针算术。 - Julius
4个回答

6
我在正式规范中没有找到关于“数组对象”的明确定义,但在C11标准草案的7.22.3中查看内存分配函数后,我找到了以下内容:
“aligned_alloc”、“calloc”、“malloc”和“realloc”函数按顺序分配的存储器的顺序和连续性是未指定的。如果分配成功,则返回的指针适当地对齐,以便可以将其赋值给具有基本对齐需求的任何类型对象的指针,然后在分配的空间中用于访问这样的对象或这样的对象数组,直到显式释放空间为止。
虽然它不是非常明确,但它确实表明从这些函数返回的内存可以用作数组,因此应该应用指针算术法则。

阅读标准时,我无法真正理解休眠对象是否实际上具有类型(但仍然可以使用某些特定其他类型的lvalue访问它们,当标准允许时),或者它们“拥有”的唯一类型是用于访问它们的类型。这很令人困惑。 - cubuspl42

5

对于非语言法律目的,是的。

对于语言法律目的,我不认为使用 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
@1201ProgramAlarm:对于 int *,情况就不那么清晰了。标准对字符指针进行了特殊处理,即使尚未向其写入任何内容,也允许我们将分配的对象(存储区域)视为字节数组。但是,对于其他类型,它有关于“有效类型”的规定,这些规定很难解释。从实际角度来看,这种指针算术是有效的。从法律角度来看,其他人可以回答,或者我必须另外研究一下。 - Eric Postpischil
@EricPostpischil 谢谢!如果我正确理解了您的答案,使用unsigned char会更安全一些?您个人认为在现有项目中将uint8_t类型更改为unsigned char是否值得努力? - Julius
这完全取决于您需要保证多少可移植性。"对于非语言律师目的,是的。" 这句话总结了一切。在我能想到的每个当前系统上,没有必要从 uint8_t 更改为 unsigned char - 更改纯粹是语义上的。但是,如果您必须保证某些偏门的假设机器的最严格的标准兼容性,则考虑进行更改。(@EricPostpischil - 另一个很好的答案...) - David C. Rankin

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

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

(强调是我的。)

因此,由malloc返回的内存是一个数组,像您所做的计算指针差异是合法的。


0
根据标准规定:

当两个指针相减时,它们都必须指向同一个数组对象或者是该数组对象的最后一个元素之后。

[C17 § 6.5.6]

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


虽然 C17 引用是可以的,但目前没有 HTML 格式的副本可用。如果文本没有更改,引用C11标准-最新草案可能是一个好主意(它还提供了标准中每个段落的链接)。例如C11标准-7.22.3内存管理函数 - David C. Rankin
依赖于一个比当前标准落后8年的草案并不是一个好主意。 - IronMan
OP询问的是当前版本的语言,而不是已经被取代了9次的过去版本。 - IronMan
尝试使用Ctrl+F - 这并不难。 - IronMan
这似乎只是来自问题和Christian Gibbons答案的引语? - Ry-
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接