如何在C语言中使用指向指针的指针分配内存

4

1
请不要将指向指针的指针称为“双重指针”。后者听起来太像指向双精度的指针,这是完全不同的概念。 - Pete Becker
不要在C++中使用malloc。 - user2672107
@manni66。我正在使用C语言而不是C++。我正在寻找答案。 - user2979872
可能是为什么使用双指针或指向指针的指针?的重复问题。 - magician
谴责那些没有给出有效解释的负分评价者。 - user2065276
显示剩余2条评论
3个回答

13

首先,这段代码有几个问题 - 第一个是将malloc的结果转换为了错误的类型,并且使用了错误的类型来计算内存大小。修复转换和类型问题后,我们得到:

int **pt;

 pt = malloc( sizeof  *pt * 10 );   // allocate space for 10 int *
*pt = malloc( sizeof **pt * 10 );   // allocate space for 10 int
执行第一行后,您将拥有以下内容:
     int **                int *
    +---+                 +---+
pt: |   | --------------->|   | pt[0]
    +---+                 +---+       
                          |   | pt[1]
                          +---+
                          |   | pt[2]
                          +---+
                           ...
                          +---+
                          |   | pt[9]
                          +---+
你已经为10个 int * 对象分配了空间,pt 指向它们中的第一个。

下一行:

*pt = malloc( sizeof **pt * 10 ); // allocate space for 10 int

为10个int对象分配空间,并将pt[0]设置为指向它们的地址:

分配空间给10个int对象,并将pt[0]指向它们的地址:

     int **                int *                int 
    +---+                 +---+                +---+
pt: |   | --------------->|   | pt[0] -------->|   | pt[0][0]
    +---+                 +---+                +---+
                          |   | pt[1]          |   | pt[0][1]
                          +---+                +---+
                          |   | pt[2]          |   | pt[0][2]
                          +---+                +---+
                           ...                  ...
                          +---+                +---+
                          |   | pt[9]          |   | pt[0][9]
                          +---+                +---+

这展示了一种分配“不规则”数组的方法;你仍然可以像访问真正的二维数组一样访问它,即pt[i][j],但与真正的二维数组不同的是,行在内存中不相邻,并且每行可能具有不同的长度。通常会将其写为:

pt = malloc( sizeof *pt * ROWS );
if ( pt )
{
  for ( size_t r = 0; r < ROWS; r++ )
  {
    pt[r] = malloc( sizeof *pt[r] * COLS );
  }
}

完成所有这些步骤后,您将拥有如下内容:

     int **           int *                 int
    +---+            +---+                 +---+---+     +---+
pt: |   | ---------> |   | pt[0] --------> |   |   | ... |   | pt[0][0] - pt[0][COLS-1]
    +---+            +---+                 +---+---+     +---+
                     |   | pt[1] ------+
                     +---+             |   +---+---+     +---+
                     |   | pt[2] ---+  +-> |   |   | ... |   | pt[1][0] - pt[1][COLS-1]
                     +---+          |      +---+---+     +---+
                      ...           | 
                                    |      +---+---+     +---+
                                    +----> |   |   | ... |   | pt[2][0] - pt[2][COLS-1]
                                           +---+---+     +---+

将结果进行强制转换是一个非常好的想法,因为它允许编译器针对语句pt = (int*) malloc(sizeof(int)*10);发出一条消息。 - Vlad from Moscow
@VladfromMoscow:使用sizeof *pt而不是sizeof(int)可以消除特定问题。无论T如何,T *p = malloc(sizeof *p * N);始终会做正确的事情。 - John Bode
你确定吗?例如考虑 char *p = malloc( sizeof( struct A ) ); 和 char *p = malloc( sizeof *p ); - Vlad from Moscow
@VladfromMoscow:我不确定你想表达什么意思——如果我的目标是p,我将使用sizeof *p,而不是sizeof some_arbitrary_type_that_isn't_the_same_type_as_*p - John Bode
这并不总是正确的。例如,您需要分配一个字符缓冲区来复制一个结构,或者您需要分配一个一维数组来容纳一个二维数组等等。 - Vlad from Moscow
显示剩余2条评论

3
以下代码有误,编译器应该报告类型错误:
int** pt;     
pt = (int*) malloc(sizeof(int)*10);

另一个错误的原因是这里的pt实际上没有指向任何可用的内容:

int** pt;     
*pt = (int*) malloc(sizeof(int)*10);

指向T的指针是一种类型为T *的变量,它可能包含某些内存的地址,该内存可能包含T类型的元素。
+------+
|      | pointer to T 
+------+
    |
    v
+-------------+-------------+-------------+
|             |             |             | elements of type T
+-------------+-------------+-------------+ 

例如,在 C 语言中,要得到所绘制的内容,你可以这样写:
int *pi;
pi = malloc(sizeof(int)*3);

如果您有一个指向指针的T,那么图表可能如下所示:
+------+
|      | pointer to pointer to T 
+------+
    |
    v
+------+------+------+
|      |      |      | pointers to T 
+------+------+------+
    |      |      |     +-------------+-------------+-------------+
    |      |      +---->|             |             |             | elements of type T
    |      |            +-------------+-------------+-------------+ 
    |      |     +-------------+-------------+
    |      +---->|             |             | elements of type T
    |            +-------------+-------------+ 
    |
    v
+-------------+-------------+-------------+-------------+
|             |             |             |             | elements of type T
+-------------+-------------+-------------+-------------+ 

代码可以是这样的:

int **ppi;
ppi = malloc(sizeof(int *)*3);
ppi[0] = malloc(sizeof(int)*3);
ppi[1] = malloc(sizeof(int)*2);
ppi[2] = malloc(sizeof(int)*4);

当然,malloc可能会失败,返回值应该与失败进行测试。

0
使用类型转换,您帮助编译器在此代码片段中找到了一个错误。
int** pt; 

pt = (int*) malloc(sizeof(int)*10);

例如,错误信息可能如下所示:
 error: assignment from incompatible pointer type [-Werror=incompatible-pointer-types]
  pt = (int*) malloc(sizeof(int)*10);
     ^

没有进行转换,编译器可以接受这个明显无效的代码,因为函数malloc的返回类型是void *,而类型为void *的指针可以分配给任何其他类型的对象指针。在赋值的右侧,计算出的表达式的类型是int *,而在赋值的左侧,存在一个类型为int **的对象,没有从int *到int **的隐式转换。
此代码片段
int** pt; 

*pt = (int*) malloc(sizeof(int)*10);

由于另一个原因无效。指针pt未使用有效对象的地址进行初始化。如果指针具有自动存储期限,则其值可能不确定;如果指针具有静态存储期限,则其值为NULL。在任何情况下,对其进行解引用将导致未定义行为。

因此,正确的写法应该是

int* pt; 
^^^^^^^
pt = (int*) malloc(sizeof(int)*10);

然而,这个结构

int** pt; 

//...

*pt = (int*) malloc(sizeof(int)*10);

可以在某些上下文中使其有效。
假设您声明了一个指针。
int *pt;

如果您想在函数中初始化指针,则需要通过引用将指针传递给函数。否则,函数将处理指针的副本,在这种情况下,原始指针将不会在函数中被分配。

因此,相应的代码片段可以如示例程序所示。

#include <stdlib.h>
#include <stdio.h>

size_t f( int **pt )
{
    const size_t N = 10;

    *pt = (int*) malloc( sizeof( int ) * N );

    if ( *pt )
    {
        int value = 0;
        for ( size_t i = 0; i < N; i++ ) ( *pt )[i] = value++;
    }

    return *pt == NULL ? 0 : N;
}

int main( void )
{
    int *pt;

    size_t n = f( &pt );

    if ( n )
    {
        for ( size_t i = 0; i < n; i++ ) printf( "%d ", pt[i] );
        putchar( '\n' );
    }

    free( pt );
}

程序输出为

0 1 2 3 4 5 6 7 8 9

你不能像在主函数中这样传递指向 f() 的指针,比如 f(pt)。如果不行,为什么? - user2979872
@user2979872 实际上我在我的回答中已经解释了为什么这是不可能的。您能详细说明有什么不清楚的地方吗? - Vlad from Moscow
不清楚的是f(pt)和f(&pt)之间的区别。由于您将pt声明为int *pt,我想知道pt和&pt之间的区别。如果我理解正确,&pt表示pt的地址,而pt指向pointee的地址(仅在像pt = &a这样分配时,其中a被声明为int a = 5)。否则,pt指向无效地址。如果我理解有误,请纠正我。 - user2979872
@user2979872,pt和&pt是不同的类型,并且这在答案中已经写明。因此编译器会发出错误,因为参数的类型与参数的类型不对应。我们将要在函数中更改原始的pt。因此,我们必须通过引用传递它,否则函数将处理pt的副本,而副本(而不是原始的pt)将在函数中被更改。 - Vlad from Moscow
@user2979872,很抱歉我不理解你在评论中的意思。表达式&pt会产生变量pt本身的地址。指针就像其他对象一样,因此您可以使用运算符&来获取其地址。 - Vlad from Moscow
首先感谢您的耐心等待。我想要理解的是,在没有任何*或&的情况下,&pt和pt本身之间的区别。 - user2979872

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