9

我有这个工作代码:

#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

作为 CI 新手,我并不确定发生了什么或为什么输出会像那样“损坏”。虽然双指针似乎允许您在不知道类型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;
}
4

4 回答 4

6

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

  1. 流行音乐

您需要先递减len(您的推送在递增后正确)。这是一个堆栈。

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. 字符串

你想要的是一个指向字符的指针, a char *, forstr1str2, 因为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. 分配

您的程序运行是因为您的分配很大,并且items在 内部struct,它的空间很可能被整个分配覆盖。但这是一个假设(很可能,公平地说),在某些情况下可能导致不确定的行为。

但为了更清洁,由于您有两个要分配的实体,因此需要两次分配

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改用。

于 2019-03-27T01:51:59.890 回答
5

这里有一个问题:

void mypush(void* state) {
   DATA data = { state };
   int pos = collection.len++;
   collection.items[pos] = &data;
}

请注意,此函数的最后一行将指向局部变量的指针存储data到您的items数组中。但是一旦mypush()函数返回,该局部变量就会被销毁,这意味着您存储到数组中的指针不再有效!(它现在是一个悬空指针)当您稍后尝试从那个现在无效的指针中读取时,很可能会发生分段错误(它会调用未定义的行为,在这种情况下,会导致崩溃)

为避免这种情况,只需直接存储state变量,根本不涉及局部data变量。您可以根据需要将其他指针类型转换为(和从)void *(只要您小心确保您的转换与指针指向的数据的实际类型相匹配 - 使用 void 指针,编译器不会告诉您您是否正在转换为不合适的类型!)

于 2019-03-23T04:13:07.173 回答
3

修改后的代码有两个主要问题。第一个是在mypop函数中:

void
mypop(void** val) {
  puts(collection->items[collection->len]);
  *val = collection->items[collection->len--];
}

collection->len进入函数时,数组中共有 ,collection->items最后一个的索引为collection->len - 1collection->items[collection->len]读取尚未写入的数组成员也是如此,并且分配的内存在写入之前具有不确定的值。因此,当您调用puts此值时,您将取消引用无效指针。这会调用未定义的行为。在您的机器上它打印“(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函数需要 a void **,即 a 的地址void *,但您传递的是 a 的地址char。当mypopthen 分配给 时*val,它会尝试写入sizeof(void *)字节(很可能是 4 或 8 个字节)来分配值,但str1大小str2仅为sizeof(char) == 1字节。所以这意味着*val = ...将过去写入不属于它的相邻内存str1str2这再次调用未定义的行为。

由于 achar *是存储在您的堆栈中的内容,因此它应该是char *您传递给的 a 的地址mypop。所以 makestr1str2指向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) );

这可以。具有灵活数组成员的结构的大小不包括该成员的大小。因此,当为这样的结构分配空间时,您需要结构的大小加上一些数组元素的大小。这正是您所做的:为结构分配空间,并使用能够容纳 1000 个元素的灵活数组成员。

于 2019-03-27T02:04:01.657 回答
0

更改了一些内容,在下面的代码中进行了注释。

您需要注意,您必须分配一个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;
}
于 2019-03-29T16:15:31.937 回答