在结构中使用指针数组重新分配

Realloc with an array of pointers in a struct

在 C89 中,以下代码可以正常工作:

typedef struct{
    int size;
    int **ptr;
}foo;

int main()
{
    foo *var;

    var = malloc(sizeof(foo) + 2 * sizeof(int*));

    int a = 3, b = 6;

    var->ptr[0] = &a;
    var->ptr[1] = &b;

    printf("%d %d\n", (*var->ptr[0]), (*var->ptr[1]));

    return 0;
}

执行时显示:3 6

但是如果我替换

var = malloc(sizeof(foo) + 2 * sizeof(int*));

var = malloc(sizeof(foo));
var = realloc(var, sizeof(foo) + 2 * sizeof(int*));

它在没有任何警告的情况下编译,但我在以下行遇到分段错误:

var->ptr[0] = &a;

我真的不知道我做错了什么,所以我很感激任何意见。我是初学者,如果我的错误很明显,我深表歉意。

谢谢。

我认为你的声明是错误的。以下行假设 ptr 指向一些内存,而实际上它根本没有被初始化!

var->ptr[0] = &a;

如果你的编译器支持的话,是不是应该更像这样:

typedef struct{
    int size;
    int* ptr[0];
}foo;

否则,将 ptr[0] 更改为 ptr[1],并根据结构已包含一个元素这一事实调整您的计算。

注意:一旦写入错误的地址,结果是不确定的。你那里有问题,但它只会在一种情况下出现。在这两种情况下问题仍然存在。

您需要修复结构:

typedef struct{
    int size;
    int *ptr;
}foo;

代码:

 var = malloc(sizeof(foo));
 var->ptr = malloc(2 * sizeof(int));

 int a = 3, b = 6;
 // store a and b in array, not their addresses
 var->ptr[0] = a;
 var->ptr[1] = b;

假设您需要数组中的 2 个条目。当然,忽略我没有测试 malloc().

结果的事实

如果要调整数组大小,只需重新分配 var->ptr 而不是 var

我会说这在 C89 中也是错误的。

malloc(sizeof(foo) + 2 * sizeof(int*));

分配的超出您的需要,但未达到您的预期。

+-------+------------+---------+--------+
|  size | ptr        |  int*   | int*   |
+-------+------------+---------+--------+

|  sizeof foo        | 2 * sizeof(int*) |

如你所见,var->foo 仍然是未初始化的,不管有多少额外的字节 你已经分配了,所以

var->ptr[0] = &a;
var->ptr[1] = &b;

会失败。

你应该做的是:

var = malloc(sizeof *var);

var->ptr = malloc(2 * sizeof *var->ptr);

var->ptr[0] = &a;
var->ptr[1] = &b;

然后看起来像这样

                                     | int |     | int |

                                     +-----+     +-----+
                                     |  a  |     |  b  |
                                     +-----+     +-----+
                                         ^         ^
                                         |         |
                                         |         |
+-------+------------+  points to     +---------+--------+
|  size | ptr        | ------------>  | int*    | int*   |
+-------+------------+                +---------+--------+

|  sizeof foo        |                | 2 * sizeof(int*) |

不要忘记检查 malloc 的 return 值并释放内存 之后。

编辑

可行的是:

var = malloc(sizeof(foo) + 2 * sizeof(int*));
var->ptr = (int**) ( ((char*) var) + sizeof *var );

您将 var->pointer 设置为指向 struct 的内存。尽管 这可能有用,我认为它很丑陋,我会拒绝提交 有这样的同事。

问题是你没有分配 ptr 指向任何东西,所以它指向随机内存,并且 ptr[index] 的任何访问都是 未定义的行为.

改变 ptr 的声明就像 Jonathan 的回答建议的那样是最安全的选择,例如:

typedef struct{
    int size;
    int* ptr[1]; // or 0 if your compiler supports it
}foo;

int main()
{
    foo *var;

    var = malloc(offsetof(foo, ptr));
    if (var) {
        var->size = 0;
        ...
    }

    foo *newvar = realloc(var, offsetof(foo, ptr) + (2 * sizeof(int*)));
    if (newvar) {
        var = newvar;
        var->size = 2;

        int a = 3, b = 6;

        var->ptr[0] = &a; // <-- OK!
        var->ptr[1] = &b; // <-- OK!

        printf("%d %d\n", *(var->ptr[0]), *(var->ptr[1]));
    }

    free(var);
    return 0;
}

但是,如果出于某种原因,您无法更改 ptr 的声明(例如,foo 与现有的 API 一起使用),那么您可以这样做改为:

typedef struct{
    int size;
    int **ptr;
}foo;

int main()
{
    foo *var = malloc(sizeof(foo));
    if (var) {
        var->size = 0;
        var->ptr = NULL; // <-- add this!
        ...
    }

    foo *newvar = realloc(var, sizeof(foo) + (2 * sizeof(int*)));
    if (newvar) {
        var = newvar;
        var->size = 2;
        var->ptr = (int**)(var + 1); // <-- add this!

        int a = 3, b = 6;

        var->ptr[0] = &a; // <-- OK!
        var->ptr[1] = &b; // <-- OK!

        printf("%d %d\n", *(var->ptr[0]), *(var->ptr[1]));
    }

    free(var);
    return 0;
}

在后一种情况下,请确保每次要(重新)分配 var 时都执行相同的 ptr 分配。我会将该逻辑包装在一组辅助函数中,例如:

typedef struct{
    int size;
    int **ptr;
}foo;

foo* createFoo(int size)
{
    foo *var = malloc(sizeof(foo) + (size * sizeof(int*)));
    if (var) {
        var->size = size;
        var->ptr = (int**)(var + 1);
    }
    return var;
}

foo* resizeFoo(foo **var, int newSize)
{
    foo *newvar = realloc(*var, sizeof(foo) + (newSize * sizeof(int*)));
    if (newvar) {
        newvar->size = newSize;
        newvar->ptr = (int**)(newvar + 1);
        *var = newvar;
    }
    return newvar;
}

int main()
{
    foo *var = createFoo(0);
    ...

    if (resizeFoo(&var, 2)) {
        int a = 3, b = 6;

        var->ptr[0] = &a; // <-- OK!
        var->ptr[1] = &b; // <-- OK!

        printf("%d %d\n", *(var->ptr[0]), *(var->ptr[1]));
    }

    free(var);
    return 0;
}

在 Visual Studio、Windows 10 中,您的两个示例均无效。这是因为结构在这两种情况下都未初始化。

箭头操作:

foo->bar

是 shorthand 写给:

(*foo).bar

因此,如果 "foo" 结构变量未初始化,根据编译器的不同,结构中的指针将指向内存中的随机位置。

因此,当您这样使用箭头运算符时:

var->ptr[0] = &a;

您所做的是:

(*var).ptr[0] = &a;

所以,基本上,您试图将 "a" 的地址写入内存中您不应该访问的位置。这就是您遇到分段错误的原因。

但是,如果您改为初始化它:

int goodmemorylocation = 0;
var->ptr = &goodmemorylocation; // You change the location of your pointer to an accessible location in memory

然后你的代码在我的系统中适用于这两种情况。

也许,在您的系统中,您使用 malloc 的第一个示例以某种方式指向您内存中的正确地址。这就是它起作用的原因。