在 C 中,是否可以在堆上分配“char[]”?

In C is it possible to allocate a `char[]` on the heap?

提问人:gowerc 提问时间:11/14/2023 最后编辑:Daniel A. Whitegowerc 更新时间:11/14/2023 访问量:123

问:

我目前正在尝试学习 C,所以如果这是一个愚蠢的问题,我深表歉意。之前有许多问题特别强调了这一点。char* != char[]

但是,我能找到的所有堆数组示例似乎都使用例如char*

char *heap_string = (char *)malloc(50*sizeof(char) +  1);

所以我的问题是;是否有可能在堆上有一个(这甚至有意义吗?还是只是堆栈对象?char[]char[]

(我应该补充一点,我在这里没有具体的目标,我只是想学习语言/了解引擎盖下发生的事情)。

阵列 C malloc

评论

0赞 Daniel A. White 11/14/2023
它是你的记忆以及你选择如何解释它
1赞 Andrew Henle 11/14/2023
你怎么称呼堆上分配的内存并不重要 - et al 返回的指针仍然是一个指针。malloc()
1赞 n. m. could be an AI 11/14/2023
首先,C 中没有对象。所有示例都有 类型的对象,其中是一些整数常量(特定于每个变量)。 只是一个速记符号,可以(但不必)用于声明它们。char[]char[N]Nchar[]
1赞 n. m. could be an AI 11/14/2023
不会声明堆上的对象。 返回一个指针,并将该指针解释为指向特定类型的对象。该类型可以是 。因此,您有两个对象,一个位于堆上,另一个位于堆栈上并保存该堆对象的地址。mallocchar[N]char[N]char*
2赞 Ted Lyngmo 11/14/2023
不相关,但您当前的分配可以减少到: - 或只是char *heap_string = malloc(sizeof(char[50 + 1]));char *heap_string = malloc(50 + 1);

答:

2赞 Mooing Duck 11/14/2023 #1

可以把一个放在堆上,但它令人讨厌和混乱,而且不是特别有意义。在我的脑海中,我相信语法是:char[50]

char (*heap_string)[50] = (char (*)[50])malloc(50*sizeof(char));

请注意,在您的代码中,类型是“指向未知数的指针”。在此代码中,类型为 “指向未知数 ” 的指针。所以指的是第一个数组,而两者都指的是第二个数组(或者,如果我们为它分配了空间的话)。heap_stringcharheap_stringchar[50]heap_string[0]char[50]heap_string[1]heap_string+1char[50]

一般来说,开发人员发现指向数组的指针非常混乱,并且几乎肯定会写出错误,所以没有人这样做。更糟糕的是,现在代码仅适用于 ,而不适用于任何其他长度,因此代码也不太灵活且功能较弱。所以再说一次,没有人这样做。char[50]

另请注意,由于它是 ,因此没有额外的空间来容纳尾随的 null 字符,就像您在示例代码中所做的那样。char[50]

评论

0赞 gowerc 11/14/2023
哦,哇,你似乎在工作,感觉很脏:''' (*heap_string)[0] = 'a';(*heap_string)[1] = 'b';(*heap_string)[2] = '\0';printf(“ptr 的内存位置为:%p\n”, (void *)heap_string[0]);printf(“ptr 的内存位置为:%p\n”, (void *)heap_string[1]);printf(“值为:%s\n”, *heap_string);-------------- ptr 的内存位置为: 0x7ff458705eb0 ptr 的内存位置为:0x7ff458705ee2 值为:ab ''' 谢谢你解释这个!
0赞 Mooing Duck 11/14/2023
@EricPostpischil:(A)你太迂腐了。“在堆上分配东西”具有很好理解和具体的含义。C++规范不使用术语“堆”这一事实是无关紧要的。(B) 你也错了。据我所知,没有一个自由商店的主要实现使用过堆数据结构。en.wikipedia.org/wiki/......
0赞 tstanisl 11/14/2023
无需从 转换结果。它只是给代码增加了毫无意义的噪音。最好做到:malloc()char (*heap_string)[50] = malloc(sizeof *heap_string);
1赞 tstanisl 11/14/2023
@gowerc,确实如此。请参见 godbolt。您必须使用 C++ 编译器。不要这样做,因为 C 不是 C++ 的子集。
1赞 gowerc 11/14/2023
抱歉,你是对的,我正在使用显然在 mac 上映射到 -> 尽管因为我使用的是扩展而不是 clang,所以决定它会编译为 c++ :(gccApple clang version 14.0.3.C.c
3赞 Eric Postpischil 11/14/2023 #2

数组是对象,C 中的对象是“执行环境中的数据存储区域,其内容可以表示值”(C 2018 3.15)。因此,当您使用 分配内存时,该内存可以用作 .C 2018 7.22.3 表示,返回的指针(如果成功)可以“用于访问分配的空间中的此类对象或此类对象的数组(直到空间被显式解除分配)”。charmallocchar []

假设在指向的位置有一个数组,如果我们选择这样使用它,问题是我们通常通过指向它们的各个元素来使用数组。成功后,有一个 50 的数组 ,我们可以使用 访问它的元素。charchar *p = malloc(50);charpp[i]

如果要使用 或 类型引用数组,可以使用指向该类型的指针 来执行此操作,请使用 或 。char []char [50]char (*p)[] = malloc(50);char (*p)[50] = malloc(sizeof *p);

有了这些,在不访问其各个元素的情况下使用数组的方法有限。由于历史原因,C 语言被设计为主要通过其单个元素访问数组,而不是作为整个对象。

这不会为您提供数组的名称;您没有作为数组的某个变量。但是,您可以将其作为数组的名称使用。这种差异无关紧要;对于数组的名称,您几乎无法执行 。A*pA*p

一个区别在于采用数组的大小。如果将某个数组声明为静态或自动数组,而不是动态分配的数组,则可以使用 .使用上面的声明,您还可以使用 .使用 or 声明时,无法使用 .但是,这通常无关紧要,因为对于动态分配的数组,程序通常自行处理其大小。它必须将大小传递给 ,因此在创建数组时它具有该值,并且只需要根据需要保留它以供其他用途。因此,动态分配数组的程序不需要在数组上使用。sizeof Achar (*p)[50]sizeof *pchar *pchar (*p)[]sizeofmallocsizeof

评论

0赞 tstanisl 11/14/2023
这是否意味着从返回的指针在技术上指向所有类型的数组?malloc()
1赞 Eric Postpischil 11/14/2023
@tstanisl:指针可用于访问满足对齐要求的任何类型的数组(或单个对象)。( 返回一个满足基本对齐要求的地址,因此它可以用于所有基本类型。根据你如何解释 C 关于有效类型的规则,它实际上并不指向任何特定类型的数组,直到你执行一些操作将其视为这样的数组。malloc
0赞 tstanisl 11/14/2023
操作等同于 。这是否意味着指针算术将对象的类型设置为数组类型?p[idx]*(p + idx)
0赞 Eric Postpischil 11/14/2023
@tstanisl:这些规则有一些复杂性,包括取决于你是否使用字符类型以及你是在阅读还是写作。有关详细信息,请参阅 C 标准中有关有效类型的规则,或在 Stack Overflow 中搜索“[C] ”有效类型“”或发布问题。
0赞 Lundin 11/14/2023
@tstanisl 不是指针算术,而是取消引用和存储。C17 6.5 “如果通过具有非字符类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问和不修改存储值的后续访问的对象的有效类型。”从 malloc 返回的块是没有声明类型的对象。关于如何将数组对象与数组项类型的单个“标量”对象区别对待,规则是模糊的。
1赞 Lundin 11/14/2023 #3

的确,和是不同的东西。未指定大小的数组是不完整的数组,因为它没有指定大小。通常我们不能使用这样的数组,会导致编译器错误。char*char[]char[]char arr[];

然而,作为一个特殊规则,如果我们提供一个初始值设定项,那么数组将获得一个合适的大小来包含初始值设定项列表中的所有项目:实际上给了我们一个(2 个字母 + null 终止符)。char array[] = "hi"char[3]

另一个特殊规则是,当我们将数组编写为函数参数时,该参数会隐式调整为指向该数组第一项的指针。因此,我们可以写成,然后变成一个.无论我们为数组参数指定什么大小,这种调整(有时称为“数组衰减”)都会发生,这就是为什么 C 允许在那里使用空。void func (char array[])arraychar*[]

由于调用不符合上述语法的任何一种用法,因此我们无法真正指定动态声明的不完整类型的数组。但是,我们可以创建一个指向此类数组的指针:mallocchar[]

char (*heap_string)[] = malloc(50*sizeof(char) +  1);

由于 ( 保证始终为 1,我们也可以这样做。或。都是等效的。sizeof(char)malloc(50+1)malloc(sizeof(char[50+1]))

这是一个繁琐的指向不完整类型的指针。这意味着每次我们想要访问数组时都必须写入。这很不方便,也很难阅读,因此召集只是使用。heap_string(*heap_string)[i] = 'a';char* heap_string = malloc(...

然而,上述语法对于多维数组来说非常方便。然后,我们可以跳过最左边的数组边界:

int (*arr)[2][3] = malloc( sizeof(arr[1][2][3]) );

然后我们可以以 .在指针中跳过一个维度意味着我们不必这样做,这将是丑陋和繁琐的。arr[i][j][k](*arr)[i][j][k]

评论

0赞 gowerc 11/14/2023
谢谢。那么 和 之间有什么实际区别吗?我幼稚的假设是前者适用于类型,而后者则不适用于类型?void func (char x[])void func (char * x)char (*)[N]
1赞 Lundin 11/14/2023
@gowerc 这两个例子是 100% 等效的。“调整”发生在编译时的早期。您可以 godbolt.org/z/cYK111WzW 查看此示例。我们不能在 main() 中声明一个普通的,所以我使用了指向一个的指针。编译器在 main() 中将数组和指针视为不同的类型。但在功能内部,它们都是因为调整。char[]char*