如何在 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 *(只要您小心确保您的转换与指针指向的数据的实际类型匹配——使用空指针,如果您转换为不合适的类型,编译器不会告诉您!)

有一些问题需要解决,但对于初学者来说还不错。

  1. 流行

您需要先递减 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
  1. 海峡

你想要的是一个指向字符的指针,一个 char *,用于 str1str2,因为 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;

这应该可以修复明显损坏的显示。但是还有一些有趣的事情

  1. 分配

您的程序运行是因为您的分配很大,并且 itemsstruct 内,它的 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;
  1. 进口

#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 字节)来分配值,但是 str1str2 的大小仅为 sizeof(char) == 1 字节。所以这意味着 *val = ... 正在将 str1str2 写入不属于它的相邻内存中。这再次调用未定义的行为。

因为 char * 是存储在堆栈中的内容,它应该是您传递给 mypopchar * 的地址。所以让 str1str2 指向 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;
}