【问题标题】:How do I allocate memory and determine array sizes dynamically in C?如何在 C 中动态分配内存和确定数组大小?
【发布时间】:2011-03-15 17:56:31
【问题描述】:

我正在尝试从 python 背景自学 C。我目前的小问题是尝试减少对数组长度等事情的硬编码,并根据输入动态分配内存。

我编写了以下程序。我希望社区提供建议以通过以下方式对其进行修改:

1.) 使 first 和 last Name 元素可变长度。目前它们的长度被硬编码为MAX_NAME_LENGTH。这将涉及更改 Names structdeclaration 以及我为其元素分配值的方式。

2.) 奖励:想办法逐步向name_list 数组添加新元素,而无需事先确定其长度。基本上使它成为一个可扩展的列表。

/* namelist.c 

   Loads up a list of names from a file to then do something with them.

*/
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>

#define DATAFILE "name_list.txt"
#define DATAFILE_FORMAT "%[^,]%*c%[^\n]%*c"
#define MAX_NAME_LENGTH 100

typedef struct {
  char first[MAX_NAME_LENGTH];
  char last[MAX_NAME_LENGTH];
} Name;


int main() {
  FILE *fp = fopen(DATAFILE, "r");

  // Get the number of names in DATAFILE at runtime.
  Name aName;
  int lc = 0;
  while ((fscanf(fp, DATAFILE_FORMAT, aName.last, aName.first))!=EOF) lc++;
  Name *name_list[lc];

  // Now actually pull the data out of the file
  rewind(fp);
  int n = 0;
  while ((fscanf(fp, DATAFILE_FORMAT, aName.last, aName.first))!=EOF)
  {
    Name *newName = malloc(sizeof(Name));
    if (newName == NULL) {
      puts("Warning: Was not able to allocate memory for ``Name`` ``newName``on the heap.");
    } 
    memcpy(newName, &aName, sizeof(Name));
  name_list[n] = newName;
  n++;
  }

  int i = 1;
  for (--n; n >= 0; n--, i++) {
    printf("%d: %s %s\n", i, name_list[n]->first, name_list[n]->last);
    free(name_list[n]);
    name_list[n] = NULL;
  }

  fclose(fp);
  return 0;
}

name_list.txt的示例内容:

Washington,George
Adams,John 
Jefferson,Thomas
Madison,James

更新 1:

我已经按照@Williham 的建议实现了一个链表和一些辅助函数,结果如下。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define DATAFILE "name_list.txt"
#define MAX_NAME_LENGTH 30
#define DATAFILE_FORMAT "%29[^,]%*c%29[^\n]%*c"

static const int INPUT_BUFFER_SIZE_DEFAULT = sizeof(char) * MAX_NAME_LENGTH;

typedef struct _Name Name;

struct _Name {
  char *first;
  char *last;
  Name *next;
};

int get_charcount(char *str);

Name * create_name_list(char *filename);
void print_name_list(Name *name);
void free_name_list (Name *name);

int main() {

  // Read a list of names into memory and 
  // return the head of the linked list.
  Name *head = create_name_list(DATAFILE);

  // Now do something with all this data.
  print_name_list(head);

  // If you love something, let it go.
  free_name_list(head);
  head = NULL;
  return 0;
}

int get_charcount (char *str) 
{
  int input_length = 1;
    while (str[input_length] != '\0')
    {
      input_length++;
    }
  return input_length;
}

Name * create_name_list(char *filename)
{
  FILE *fp = fopen(DATAFILE, "r");
  char *first_input_buffer = malloc(INPUT_BUFFER_SIZE_DEFAULT);
  char *last_input_buffer = malloc(INPUT_BUFFER_SIZE_DEFAULT);

  Name *firstNamePtr;
  Name *previousNamePtr;
  while ((fscanf(fp, DATAFILE_FORMAT, last_input_buffer, first_input_buffer))!=EOF)
  {
    Name *newNamePtr = malloc(sizeof(Name));

    if (previousNamePtr) 
    {
      previousNamePtr->next = newNamePtr;
      previousNamePtr = newNamePtr;
    } 
    else 
    {
      firstNamePtr = previousNamePtr = newNamePtr;
    }

    char *temp_buffer = malloc(get_charcount(first_input_buffer));
    strcpy(temp_buffer, first_input_buffer);
    newNamePtr->first = malloc(get_charcount(first_input_buffer));
    strcpy(newNamePtr->first, temp_buffer);


    realloc(temp_buffer, get_charcount(last_input_buffer));

    strcpy(temp_buffer, last_input_buffer);
    newNamePtr->last = malloc(get_charcount(last_input_buffer));
    strcpy(newNamePtr->last, temp_buffer);

    free(temp_buffer);
    temp_buffer = NULL;
  }
  previousNamePtr->next = NULL;
  previousNamePtr = NULL;
  free(first_input_buffer);
  free(last_input_buffer);
  first_input_buffer = NULL;
  last_input_buffer = NULL;
  fclose(fp);

  return firstNamePtr;
}

void print_name_list (Name *name)
{
  static int first_iteration = 1;
  if (first_iteration) 
  {
    printf("\nList of Names\n");
    printf("=============\n");
    first_iteration--;
  }
  printf("%s %s\n",name->first, name->last);
  if (name->next)
    print_name_list(name->next);
  else
    printf("\n");
}

void free_name_list (Name *name)
{
  if (name->next)
    free_name_list(name->next);
  free(name->first);
  free(name->last);
  name->first = NULL;
  name->last = NULL;
  name->next = NULL;
  free(name);
  name = NULL;
}

【问题讨论】:

  • @Alok:如果是作业,那是非常隐蔽且礼貌的作业要求。
  • @Alok 这是我给自己布置的作业。我从 MIT 的 Practical Programming in C (ocw.mit.edu/courses/electrical-engineering-and-computer-science/…) 开始的问题略有不同。我喜欢从列表中读取数据的前提,因为它在 python 中非常熟悉,所以我在问题中添加了一些额外的位,这些位在 python 使微不足道的类似问题中很熟悉。我自己研究它,直到我不完全确定如何继续,谷歌搜索,阅读并在这里询问。

标签: c dynamic arrays struct malloc


【解决方案1】:

由于还没有人提到它,如果你使用 *scanf 来读取不受信任的输入字符串,你应该总是使用 最大字段宽度说明符。喜欢

scanf("%19s", str);

请注意,这指定了不包括终止 NUL 的最大字符串长度,因此在此示例中,str 应为 20 个字符长。

如果你不限制这样的 scanf 输入转换,在缓冲区溢出时,你不仅会得到未定义的行为,还会出现安全漏洞。

回到你关于动态增长缓冲区的问题。假设您正在读取一些由行组成的输入,并且每行最多可以有 78 个字符宽。在这种情况下,您知道每行的最大长度,但不知道整个输入的最大长度。一种常见的方法是分配一些默认空间,如果需要增加该空间,请将其增加 *2,这样就不需要多次 realloc()。

【讨论】:

    【解决方案2】:

    您是否开始阅读有关链表的内容?它可以帮助您拥有一个动态的结构。

    您还可以检查数组和链表之间的区别。 char 指针可以是链表的成员,可以动态分配内存。

    查看来自 stackoverflow 的 link 以获取更多信息。

    【讨论】:

      【解决方案3】:

      您必须有一个最大长度的数组来获取输入 - 您不知道输入的长度。但是,您只需要一个该长度的数组。获得输入后,您可以获取它的长度并分配一个大小合适的数组来存储名称。

      至于增加您的列表,您可以使用realloc 或当然使用链接列表。

      【讨论】:

      • 所以最大长度数组只是一个输入缓冲区,没有办法绕过硬编码该值?如果我尝试粘贴比输入缓冲区数组的硬编码最大长度更长的 char 数组,我会得到未确定的行为吗?
      • @Prairiedogg - 没错,您必须有一个已知长度的输入缓冲区,如果您尝试读取的内容超过它的容量,您将得到未定义的行为 - 缓冲区溢出等。
      【解决方案4】:

      一个非常简单的方法是根本不使用数组,而是使用链表:

      这可以通过多种方式完成,但最简单的可能是修改 Name 结构如下:

      typedef struct _Name Name;
      
      struct _Name {
        char *first;
        char *last;
        Name *next;
      };
      

      使用 char * 而不是 char[] 将需要一些strcpying,但这实际上既不存在也不存在。要扩展数组,您现在可以一次简单地 malloc 这些元素;并适当设置下一个。

      注意:当你创建新的尾部元素时,记得设置next为NULL。

      【讨论】:

      • 谢谢,我已经在更新 1 中实现了你的建议。我唯一不满意的部分是,当我将名字和姓氏读入缓冲区时,如果有名字,事情就会出错比INPUT_BUFFER_SIZE_DEFAULT 长。关于通常如何处理的任何提示?否则,很好的答案,感谢代码 sn-p!
      • 如果你在 Linux 上,有一个相当不错的小函数 getline() 会为你分配整行的缓冲区。然后可以从容地从中提取信息。如果你不是,你最好使用适当大小的缓冲区(80 或 120 字节可能会这样做),并使用gets() 读取整行。如果遇到过长的行,请使用realloc() 扩展缓冲区,并将其保持为新的更大尺寸,以备不时之需。 (记住要跟踪它有多大。)
      【解决方案5】:

      没有办法在 C 中扩展数组。您所能做的就是分配更大的内存块并将项目复制过来。这就是 Python 在幕后所做的。

      也无法确定数组的大小,因为它只存储项目而没有其他信息(Python 数组将长度存储在元素旁边。)您可以在数组末尾放置一个标记并计算元素在您点击标记之前,这就是字符串的工作方式(空字符 '\0' 是标记。)

      【讨论】:

      • 相反,realloc() 调用的主要目的是调整数组的大小,尽管它只适用于最初使用malloc() 分配的数组。
      • @caf 谢谢,我在更新 1 中尝试了realloc()。
      • @caf: realloc 通常会实际分配新内存,然后复制内容。你只是看不到它发生,因为它在内存分配器中。
      • @Borealid:当然,它会在必要时 - 但realloc() 也可能只是为了扩大分配的大小(例如,使用 OpenBSD 分配器(openbsd.org/papers/eurobsdcon2009/otto-malloc.pdf)它几乎会总是能够这样做)
      猜你喜欢
      • 2018-11-03
      • 1970-01-01
      • 1970-01-01
      • 2012-09-15
      • 1970-01-01
      • 2022-01-02
      • 2012-11-15
      • 2015-01-27
      • 2015-08-09
      相关资源
      最近更新 更多