在结构中使用指针数组重新分配
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 的第一个示例以某种方式指向您内存中的正确地址。这就是它起作用的原因。
在 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 的第一个示例以某种方式指向您内存中的正确地址。这就是它起作用的原因。