如何在 C 中压入和弹出一个空指针
How to push and pop a void pointer in C
我有这个工作代码:
#import <stdlib.h>
#import <stdio.h>
typedef struct myarray {
int len;
void* items[];
} MYARRAY;
MYARRAY *collection;
void
mypop(void** val) {
puts(collection->items[collection->len]);
*val = collection->items[collection->len--];
}
void
mypush(void* val) {
int len = collection->len++;
collection->items[len] = val;
puts(collection->items[len]);
}
int
main() {
puts("Start");
collection = malloc( sizeof *collection + (sizeof collection->items[0] * 1000) );
collection->len = 0;
puts("Defined collection");
mypush("foo");
puts("Pushed foo");
mypush("bar");
puts("Pushed bar");
char str1;
mypop((void*)&str1);
puts("Popped bar");
puts(&str1);
char str2;
mypop((void*)&str2);
puts("Popped foo");
puts(&str2);
puts("Done");
return 0;
}
它输出:
Start
Defined collection
foo
Pushed foo
bar
Pushed bar
(null)
Popped bar
bar
Popped foo
�ߍ
Done
它应该输出这个:
Start
Defined collection
foo
Pushed foo
bar
Pushed bar
bar
Popped bar
bar
foo
Popped foo
foo
Done
作为 C 语言的新手,我不太确定发生了什么或者为什么输出是这样的 "corrupted"。似乎双指针 void**
允许您在不知道类型 的情况下传入一个指针并得到一个值 ,所以是的。但是想知道是否有人可以展示应该如何实现这段代码,以便我可以了解如何做这样的事情。
用 clang 编译:
clang -o example example.c
更新
我更新了我的代码以反映最新的答案,但仍然不确定集合的 malloc 是否正确。
#include <stdlib.h>
#include <stdio.h>
typedef struct myarray {
int len;
void* items[];
} MYARRAY;
MYARRAY *collection;
void
mypop(void** val) {
--collection->len;
puts(collection->items[collection->len]);
*val = collection->items[collection->len];
}
void
mypush(void* val) {
int len = collection->len++;
collection->items[len] = val;
puts(collection->items[len]);
}
int
main() {
puts("Start");
collection = malloc( sizeof *collection + (sizeof collection->items[0] * 1000) );
collection->len = 0;
puts("Defined collection");
mypush("foo");
puts("Pushed foo");
mypush("bar");
puts("Pushed bar");
char *str1;
mypop((void**)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void**)&str2);
puts("Popped foo");
puts(str2);
free(collection);
puts("Done");
return 0;
}
这里有一个问题:
void mypush(void* state) {
DATA data = { state };
int pos = collection.len++;
collection.items[pos] = &data;
}
请注意,此函数的最后一行将指向局部变量 data
的指针存储到您的 items
数组中。但是一旦mypush()
函数returns,那个局部变量就被销毁了,这意味着你存入数组的指针不再有效! (它现在是一个 悬垂指针 )当您稍后尝试从现在无效的指针(调用未定义的行为,在这种情况下,崩溃)读取时,很可能会发生分段错误)
为避免这种情况,只需直接存储 state
变量,根本不涉及本地 data
变量。您可以根据需要将其他指针类型转换为(和从)void *
(只要您小心确保您的转换与指针指向的数据的实际类型匹配——使用空指针,如果您转换为不合适的类型,编译器不会告诉您!)
有一些问题需要解决,但对于初学者来说还不错。
- 流行
您需要先递减 len
(您的推送正确 post-递增)。这是一个堆栈。
void mypop(void** val) {
puts(collection->items[--collection->len]);
*val = collection->items[collection->len];
}
数组从 0
开始,所以
len = 0;
items[len++] = elem1; // len is 0 for the assignment then incremented
items[len++] = elem2; // len is 1 for the assignment then incremented
然后弹出值
elem2 = items[--len]; // len is first decremented to 1
elem1 = items[--len]; // len is first decremented to 0
- 海峡
你想要的是一个指向字符的指针,一个 char *
,用于 str1
和 str2
,因为 pop()
将存储一个指针,而不是单个字符。
char *str1;
mypop((void **)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void **)&str2);
puts("Popped foo");
puts(str2);
puts("Done");
return 0;
这应该可以修复明显损坏的显示。但是还有一些有趣的事情
- 分配
您的程序运行是因为您的分配很大,并且 items
在 struct
内,它的 space 可能被整个分配覆盖。但这是一个假设(很可能,公平地说),在某些情况下可能导致 undefined behavior。
但为了更简洁,因为您有两个实体要分配,所以需要两次分配
collection = malloc( sizeof *collection );
collection->items = malloc( sizeof(collection->items[0]) * 1000 );
稍后释放。
在这种情况下,结构应该是
typedef struct myarray {
int len;
void **;
} MYARRAY
由于MYARRAY
本身很小,你也可以静态声明它
static MYARRAY collection;
- 进口
#import
已弃用,请改用 #include
。
您修改后的代码存在两个主要问题。第一个是在 mypop
函数中:
void
mypop(void** val) {
puts(collection->items[collection->len]);
*val = collection->items[collection->len--];
}
函数进入时,collection->items
数组中一共有collection->len
个,最后一个的索引为collection->len - 1
。所以 collection->items[collection->len]
正在读取一个尚未写入的数组成员,并且分配的内存在写入之前具有 不确定值 。因此,当您对该值调用 puts
时,您正在解除对无效指针的引用。这会调用 undefined behavior。在你的机器上它打印“(null)”但在我的机器上它崩溃了。
这可以通过先递减 len
来解决:
void
mypop(void** val) {
collection->len--;
puts(collection->items[collection->len]);
*val = collection->items[collection->len];
}
第二个问题是您如何保存弹出的值:
char str1;
mypop((void*)&str1);
puts("Popped bar");
puts(&str1);
char str2;
mypop((void*)&str2);
puts("Popped foo");
puts(&str2);
mypop
函数需要 void **
,即 void *
的地址,但您传递的是 char
的地址。当 mypop
然后分配给 *val
时,它会尝试写入 sizeof(void *)
字节(很可能是 4 或 8 字节)来分配值,但是 str1
和 str2
的大小仅为 sizeof(char) == 1
字节。所以这意味着 *val = ...
正在将 str1
和 str2
写入不属于它的相邻内存中。这再次调用未定义的行为。
因为 char *
是存储在堆栈中的内容,它应该是您传递给 mypop
的 char *
的地址。所以让 str1
和 str2
指向 char
:
char *str1;
mypop((void**)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void**)&str2);
puts("Popped foo");
puts(str2);
这将使您的程序 运行 正确。
此外,您还没有释放分配的内存,因此请确保在程序结束时 free(collection)
。
您还应该使用 #include
而不是 #import
来包含头文件,因为前者是标准化的,而后者是扩展。
关于您的 malloc:
collection = malloc( sizeof *collection + (sizeof collection->items[0] * 1000) );
这很好。具有灵活数组成员的结构的大小不包括该成员的大小。因此,当为这样的结构分配 space 时,您需要结构的大小加上一些数组元素的大小。这正是您所做的:为具有能够容纳 1000 个元素的灵活数组成员的结构分配 space。
更改了一些内容,在下面的代码中进行了注释。
你需要注意,你必须分配一个collection
结构,其中有一个指向1000 items
的指针也需要分配,然后再释放这些。在 C 数组中,数组从 0 开始,所以最后推送的项目是 collection->items[collection->len - 1]
.
我没有这样做,但是在使用 C 字符串时,一种常见的做法是在分配后立即将数组中的所有元素初始化为零,因此像 puts()
这样的函数永远不会导致分段错误,因为 0 被解释为字符串的结尾。
#include <stdio.h>
typedef struct myarray {
int len;
void** items;
} MYARRAY;
MYARRAY *collection;
void
mypop(void** val) {
--collection->len;
puts(collection->items[collection->len]);
*val = collection->items[collection->len];
}
void
mypush(void* val) {
collection->len++;
collection->items[collection->len - 1] = val; // 0-based index
puts((char *)collection->items[collection->len - 1]); // must cast to char*
}
int
main() {
puts("Start");
collection = malloc(sizeof(MYARRAY)); // alloc one structure
collection->items = malloc(sizeof(void *) * 1000); // that have 1000 items
collection->len = 0;
puts("Defined collection");
mypush("foo");
puts("Pushed foo");
mypush("bar");
puts("Pushed bar");
char *str1;
mypop((void**)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void**)&str2);
puts("Popped foo");
puts(str2);
free(collection->items); // need to deallocate this too
free(collection);
puts("Done");
return 0;
}
我有这个工作代码:
#import <stdlib.h>
#import <stdio.h>
typedef struct myarray {
int len;
void* items[];
} MYARRAY;
MYARRAY *collection;
void
mypop(void** val) {
puts(collection->items[collection->len]);
*val = collection->items[collection->len--];
}
void
mypush(void* val) {
int len = collection->len++;
collection->items[len] = val;
puts(collection->items[len]);
}
int
main() {
puts("Start");
collection = malloc( sizeof *collection + (sizeof collection->items[0] * 1000) );
collection->len = 0;
puts("Defined collection");
mypush("foo");
puts("Pushed foo");
mypush("bar");
puts("Pushed bar");
char str1;
mypop((void*)&str1);
puts("Popped bar");
puts(&str1);
char str2;
mypop((void*)&str2);
puts("Popped foo");
puts(&str2);
puts("Done");
return 0;
}
它输出:
Start
Defined collection
foo
Pushed foo
bar
Pushed bar
(null)
Popped bar
bar
Popped foo
�ߍ
Done
它应该输出这个:
Start
Defined collection
foo
Pushed foo
bar
Pushed bar
bar
Popped bar
bar
foo
Popped foo
foo
Done
作为 C 语言的新手,我不太确定发生了什么或者为什么输出是这样的 "corrupted"。似乎双指针 void**
允许您在不知道类型 的情况下传入一个指针并得到一个值 ,所以是的。但是想知道是否有人可以展示应该如何实现这段代码,以便我可以了解如何做这样的事情。
用 clang 编译:
clang -o example example.c
更新
我更新了我的代码以反映最新的答案,但仍然不确定集合的 malloc 是否正确。
#include <stdlib.h>
#include <stdio.h>
typedef struct myarray {
int len;
void* items[];
} MYARRAY;
MYARRAY *collection;
void
mypop(void** val) {
--collection->len;
puts(collection->items[collection->len]);
*val = collection->items[collection->len];
}
void
mypush(void* val) {
int len = collection->len++;
collection->items[len] = val;
puts(collection->items[len]);
}
int
main() {
puts("Start");
collection = malloc( sizeof *collection + (sizeof collection->items[0] * 1000) );
collection->len = 0;
puts("Defined collection");
mypush("foo");
puts("Pushed foo");
mypush("bar");
puts("Pushed bar");
char *str1;
mypop((void**)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void**)&str2);
puts("Popped foo");
puts(str2);
free(collection);
puts("Done");
return 0;
}
这里有一个问题:
void mypush(void* state) {
DATA data = { state };
int pos = collection.len++;
collection.items[pos] = &data;
}
请注意,此函数的最后一行将指向局部变量 data
的指针存储到您的 items
数组中。但是一旦mypush()
函数returns,那个局部变量就被销毁了,这意味着你存入数组的指针不再有效! (它现在是一个 悬垂指针 )当您稍后尝试从现在无效的指针(调用未定义的行为,在这种情况下,崩溃)读取时,很可能会发生分段错误)
为避免这种情况,只需直接存储 state
变量,根本不涉及本地 data
变量。您可以根据需要将其他指针类型转换为(和从)void *
(只要您小心确保您的转换与指针指向的数据的实际类型匹配——使用空指针,如果您转换为不合适的类型,编译器不会告诉您!)
有一些问题需要解决,但对于初学者来说还不错。
- 流行
您需要先递减 len
(您的推送正确 post-递增)。这是一个堆栈。
void mypop(void** val) {
puts(collection->items[--collection->len]);
*val = collection->items[collection->len];
}
数组从 0
开始,所以
len = 0;
items[len++] = elem1; // len is 0 for the assignment then incremented
items[len++] = elem2; // len is 1 for the assignment then incremented
然后弹出值
elem2 = items[--len]; // len is first decremented to 1
elem1 = items[--len]; // len is first decremented to 0
- 海峡
你想要的是一个指向字符的指针,一个 char *
,用于 str1
和 str2
,因为 pop()
将存储一个指针,而不是单个字符。
char *str1;
mypop((void **)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void **)&str2);
puts("Popped foo");
puts(str2);
puts("Done");
return 0;
这应该可以修复明显损坏的显示。但是还有一些有趣的事情
- 分配
您的程序运行是因为您的分配很大,并且 items
在 struct
内,它的 space 可能被整个分配覆盖。但这是一个假设(很可能,公平地说),在某些情况下可能导致 undefined behavior。
但为了更简洁,因为您有两个实体要分配,所以需要两次分配
collection = malloc( sizeof *collection );
collection->items = malloc( sizeof(collection->items[0]) * 1000 );
稍后释放。
在这种情况下,结构应该是
typedef struct myarray {
int len;
void **;
} MYARRAY
由于MYARRAY
本身很小,你也可以静态声明它
static MYARRAY collection;
- 进口
#import
已弃用,请改用 #include
。
您修改后的代码存在两个主要问题。第一个是在 mypop
函数中:
void
mypop(void** val) {
puts(collection->items[collection->len]);
*val = collection->items[collection->len--];
}
函数进入时,collection->items
数组中一共有collection->len
个,最后一个的索引为collection->len - 1
。所以 collection->items[collection->len]
正在读取一个尚未写入的数组成员,并且分配的内存在写入之前具有 不确定值 。因此,当您对该值调用 puts
时,您正在解除对无效指针的引用。这会调用 undefined behavior。在你的机器上它打印“(null)”但在我的机器上它崩溃了。
这可以通过先递减 len
来解决:
void
mypop(void** val) {
collection->len--;
puts(collection->items[collection->len]);
*val = collection->items[collection->len];
}
第二个问题是您如何保存弹出的值:
char str1;
mypop((void*)&str1);
puts("Popped bar");
puts(&str1);
char str2;
mypop((void*)&str2);
puts("Popped foo");
puts(&str2);
mypop
函数需要 void **
,即 void *
的地址,但您传递的是 char
的地址。当 mypop
然后分配给 *val
时,它会尝试写入 sizeof(void *)
字节(很可能是 4 或 8 字节)来分配值,但是 str1
和 str2
的大小仅为 sizeof(char) == 1
字节。所以这意味着 *val = ...
正在将 str1
和 str2
写入不属于它的相邻内存中。这再次调用未定义的行为。
因为 char *
是存储在堆栈中的内容,它应该是您传递给 mypop
的 char *
的地址。所以让 str1
和 str2
指向 char
:
char *str1;
mypop((void**)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void**)&str2);
puts("Popped foo");
puts(str2);
这将使您的程序 运行 正确。
此外,您还没有释放分配的内存,因此请确保在程序结束时 free(collection)
。
您还应该使用 #include
而不是 #import
来包含头文件,因为前者是标准化的,而后者是扩展。
关于您的 malloc:
collection = malloc( sizeof *collection + (sizeof collection->items[0] * 1000) );
这很好。具有灵活数组成员的结构的大小不包括该成员的大小。因此,当为这样的结构分配 space 时,您需要结构的大小加上一些数组元素的大小。这正是您所做的:为具有能够容纳 1000 个元素的灵活数组成员的结构分配 space。
更改了一些内容,在下面的代码中进行了注释。
你需要注意,你必须分配一个collection
结构,其中有一个指向1000 items
的指针也需要分配,然后再释放这些。在 C 数组中,数组从 0 开始,所以最后推送的项目是 collection->items[collection->len - 1]
.
我没有这样做,但是在使用 C 字符串时,一种常见的做法是在分配后立即将数组中的所有元素初始化为零,因此像 puts()
这样的函数永远不会导致分段错误,因为 0 被解释为字符串的结尾。
#include <stdio.h>
typedef struct myarray {
int len;
void** items;
} MYARRAY;
MYARRAY *collection;
void
mypop(void** val) {
--collection->len;
puts(collection->items[collection->len]);
*val = collection->items[collection->len];
}
void
mypush(void* val) {
collection->len++;
collection->items[collection->len - 1] = val; // 0-based index
puts((char *)collection->items[collection->len - 1]); // must cast to char*
}
int
main() {
puts("Start");
collection = malloc(sizeof(MYARRAY)); // alloc one structure
collection->items = malloc(sizeof(void *) * 1000); // that have 1000 items
collection->len = 0;
puts("Defined collection");
mypush("foo");
puts("Pushed foo");
mypush("bar");
puts("Pushed bar");
char *str1;
mypop((void**)&str1);
puts("Popped bar");
puts(str1);
char *str2;
mypop((void**)&str2);
puts("Popped foo");
puts(str2);
free(collection->items); // need to deallocate this too
free(collection);
puts("Done");
return 0;
}