C - 有人可以解释使用 malloc 时 (char *) 和 (char **) 的用法或目的吗?



我正在以下位置学习教程:http://www.learn-c.org/en/Arrays_and_Pointers

在代码中,他们有:

// Allocate memory for nrows pointers
char **pvowels = (char **) malloc(nrows * sizeof(char));
// For each row, allocate memory for ncols elements
pvowels[0] = (char *) malloc(ncols * sizeof(char));
pvowels[1] = (char *) malloc(ncols * sizeof(char));

但是如果我删除(字符*)和(字符**),它仍然有效:

// Allocate memory for nrows pointers
char **pvowels = malloc(nrows * sizeof(char));
// For each row, allocate memory for ncols elements
pvowels[0] = malloc(ncols * sizeof(char));
pvowels[1] = malloc(ncols * sizeof(char));

本教程没有解释这是什么,我假设它是某种类型转换,也许对于此示例,实际类型强制转换它并不重要,而是作者认为显式类型转换很重要。

我也不熟悉双"*"是什么(**)。

只是想确保我没有遗漏任何东西,并感谢您的任何解释。

源代码中的 malloc 函数正在为您的目的分配特定数据类型所需的内存量(char 变量),返回值过去必须进行类型转换,但现在不再,它仍然是一种典型的预防措施。

char *是指向 char 类型的变量的指针。char **是指向 char 类型变量的指针的指针。

我不知道已经向您解释了多少关于指针的信息,但是您可以将char指针视为char变量列表中引用的第一个char。这就是字符串在内存中的设置方式,字符串只是 char 变量的集合。

如果更深入,则可以使用字符指针指针,该指针指向字符串列表中引用的第一个字符串。这就是作者在这里所做的,他正在分配 char 指针数组所需的内存,并且由于该数组中的每个 char 指针本身都指向一组 char 值,因此它们也必须为每个字符值分配内存。

编辑:更具体地说,这些事物的"列表"称为数组。可以将 char 数组视为字符串,将 char 数组数组视为句子。

在 C 语言的原始 K&R 定义(1989 年之前)中,*alloc函数返回char *。 由于该语言不允许(并且仍然不允许)在不兼容的指针类型之间进行赋值,因此如果未char *该类型,则必须将结果显式转换为目标类型:

int *p;
...
p = (int *) malloc( n * sizeof (int) );

1989 年的标准引入了void *类型,它是上述分配规则的例外,并用作"通用"指针类型 - 可以将值void *分配给任何指针类型而无需显式强制转换,因此从 C89 标准开始,

p = malloc( n * sizeof (int) );

会工作。

请注意,这在C++中不是真的 - 在该语言中,必须将void *值转换为目标类型,因此C++您仍然必须编写

p = (int *) malloc( n * sizeof (int) );

但是如果你写的是C++,你无论如何都不应该使用malloc

在我们大多数人中,投下malloc的结果被认为是错误的。 在该语言的 C89 版本下,您实际上可以通过以下方式引入错误 这样做。 在该版本下,如果编译器看到一个函数调用,而函数调用在作用域中没有声明,它将假定该函数返回int。 所以,如果你不知何故忘记包括stdlib.h并写

int *p = (int *) malloc( n * sizeof (int) );

编译器将假定malloc返回了一个int。 如果没有强制转换,这将导致"分配中的类型不兼容"诊断;但是,包括强制转换会抑制诊断,并且您不会意识到存在问题,直到您遇到(有时非常微妙且难以诊断)运行时问题。

情况不再如此 - 该语言的 C99 修订版取消了隐式int声明,因此如果您忘记包含stdlib.h,您将获得诊断。

但是,我们中的许多人认为强制转换增加了不必要的维护负担(如果您更改目标指针的类型,您也必须更新转换),并且 IMO 它使代码更难阅读。

为什么这种做法在C中仍然存在? 几个原因:

  • 作者在 1970 年代末/1980 年代初开始编写 C 代码,但从未摆脱使用显式强制转换的习惯;
  • 作者从过时的参考文献或阅读非常旧的代码中学习;
  • 作者希望明确记录所涉及的类型。

就个人而言,我认为写*alloc电话的正确方法是

T *p = malloc( n * sizeof *p );
T *p = calloc( n, sizeof *p );
T *tmp = realloc( p, n * sizeof *p );

干净,如果你改变了p的类型,你不需要改变任何关于*alloc调用本身的信息。

编辑

我也不熟悉双"*"是什么(**)。

在 C 中可以有多个间接寻址 - 您可以有指向指针的指针、指向指针的指针等。 例如,您的pvowels对象指向指向char的指针序列中的第一个:

char **                  char *                         char
-------                  -------                        ----
+---+                    +---+                          +---+
pvowels: |   | -----> pvowels[0]: |   | ------> pvowels[0][0]:   |   | 
+---+                    +---+                          +---+     
pvowels[1]: |   | ----+   pvowels[0][1]:   |   |
+---+     |                    +---+
...      |                     ...
+---+     |                    +---+
pvowels[r-1]: |   | --+ |   pvowels[0][c-1]: |   | 
+---+   | |                    +---+
| |
| |                    +---+
| +-> pvowels[1][0]:   |   |
|                      +---+
|     pvowels[1][1]:   |   |
|                      +---+
|                       ...
|                      +---+
|     pvowels[1][c-1]: |   |
|                      +---+
...

由于pvowels指向指向char的指针序列中的第一个,其类型必须是"指向char的指针",或char **

当你为一系列T的对象*alloc内存时,目标指针必须具有T *类型:

T *p = malloc( n * sizeof *p );     // allocating a sequence of T

在上面的语句中,表达式*p的类型为T,因此sizeof *p等价于sizeof (T)

如果我们将T替换为指针类型P *,它看起来像

P **p = malloc( n * sizeof *p );    // allocating a sequence of P *

我们不是为P序列分配空间,而是为指向P指针序列分配空间。 由于p有类型P **,表达式*p有类型P *,所以sizeof *psizeof (P *)相同。

请注意,在上面的pvowels情况下,您拥有的不是2Dchar数组;pvowels指向指针序列中的第一个,每个指针都指向char序列中的第一个。 您可以像 2D 数组一样为它编制索引,但它的结构不像 2D 数组;"行"在内存中不相邻。

malloc返回一个void *指针,在较新版本的 C 中,可以将其分配给任何指针类型,而无需显式强制转换。情况并非总是如此,而且是经常被留下的历史文物。

pvowel 的类型为 char **(指向字符指针(char* )的指针)。 因此,Malloc(返回 void*)必须转换为 PVOWEL 的"类型"。

如果在 sizeof 函数中删除 char*,它的工作原理就是您想要的(malloc 保留内存 nrows 的大小为 char 类型),这可能会保存 char 指针的地址。

如果您删除强制转换字符**,我的编译器会抛出错误,正如我所期望的那样(在 CPP 中编译)。在c中,它工作得很好。

最新更新