使用动态内存分配来创建数组

14

我应该如何使用动态内存分配来创建数组?

例如,下面是一个从.txt文件中逐个读取单词并将它们逐个保存到数组中的示例代码:

代码:

char words[1000][15];

这里的1000定义了数组可以保存的单词数量,每个单词的长度不超过15个字符。

现在我希望程序能够动态分配内存以适应它所计算出的单词数。例如,一个.txt文件可能包含超过1000个单词。现在我希望程序可以计算单词数并相应地分配内存。

由于我们不能用变量代替[1000],我完全不知道如何实现我的逻辑。请在这方面帮助我。

8个回答

27
你使用指针。
具体来说,你使用一个指向地址的指针,并使用标准的C库函数调用,请求操作系统扩展堆以允许你存储所需内容。
现在,它可能会被拒绝,你需要处理。
接下来的问题是 - 如何请求一个二维数组?你请求一个指针数组,然后扩展每个指针。
例如,考虑以下内容:
int i = 0;
char** words;
words = malloc((num_words)*sizeof(char*));

if ( words == NULL )
{
    /* we have a problem */
    printf("Error: out of memory.\n");
    return;
}

for ( i=0; i<num_words; i++ )
{
    words[i] = malloc((word_size+1)*sizeof(char));
    if ( words[i] == NULL )
    {
        /* problem */
        break;
    }
}

if ( i != num_words )
{
    /* it didn't allocate */
}

这将为您提供一个二维数组,其中每个元素words[i]的大小可以在运行时确定,就像单词数一样。
当您完成后,您需要通过循环遍历数组来free()所有结果内存:
for ( i = 0; i < num_words; i++ )
{
    free(words[i]);
}

free(words);

如果你不这样做,就会造成内存泄漏。
你也可以使用calloc。区别在于调用约定和效果——calloc将所有内存初始化为0,而malloc则不会。
如果需要在运行时调整大小,请使用realloc。
此外,重要的是要注意我使用的word_size+1。在C语言中,字符串以0结尾,这需要额外的一个字符来计算。为了确保我记得这一点,我通常将变量word_size的大小设置为单词应该有的大小(我期望字符串的长度),并在malloc中明确地留下+1。然后我知道分配的缓冲区可以容纳word_size个字符的字符串。不这样做也没关系——我只是这样做,因为我喜欢明确地考虑零的问题。
这种方法也有缺点——我最近明确看到过这个作为已经发布的错误。请注意,我写了(word_size+1)*sizeof(type)——想象一下,如果我写了word_size*sizeof(type)+1会怎么样。对于sizeof(type)=1,它们是相同的,但Windows经常使用wchar_t——在这种情况下,你将为最后一个零保留一个字节而不是两个字节,并且它们是类型type的零终止元素,而不是单个零字节。这意味着你会在读取和写入时越界。
附加说明:无论你喜欢哪种方式,只要注意那些依赖它们的零终止符的东西就好了。

您的术语在此处似乎有些混淆。我期望num_words == 2意味着应该有两个单词,words [0]和words [1]包含它们。然后,您应该malloc(num_words * sizeof(char *))。 - Sam Brightman
@Sam 你说得对。我想我是指为了加上零终止符而加1。我会修复的 :) - user257111
num_words 变量从哪里来? - atw

7

虽然Ninefingers提供了使用指针数组的答案, 但只要内部数组的大小是常量表达式,您也可以使用数组的数组。这种方法的代码更简单。

char (*words)[15]; // 'words' is pointer to char[15]
words = malloc (num_words * sizeof(char[15]);

// to access character i of word w
words[w][i];

free(words);

我到处看到的num_words变量是指我们必须给数组一个长度,然后通过malloc为每个元素分配内存,而不是动态地使数组的大小变得更大,仅仅是每个元素的内存大小? - atw

2

如果你在使用C语言:

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

#define WORD_LEN 15

int resizeArray(char (**wordList)[WORD_LEN], size_t *currentSize, size_t extent)
{
  int result = 1;
  char (*tmp)[WORD_LEN] = realloc(*wordList, 
                                 (*currentSize + extent) * sizeof **wordList);
  if (tmp)
  {
    *currentSize += extent;
    *wordList = tmp;
  }
  else
    result = 0;

  return result;
}

int main(void)
{
  char *data[] = {"This", "is", "a", "test", 
                  "of", "the", "Emergency", 
                  "Broadcast", "System", NULL};
  size_t i = 0, j;
  char (*words)[WORD_LEN] = NULL;
  size_t currentSize = 0;

  for (i = 0; data[i] != NULL; i++)
  {
    if (currentSize <= i)
    {
      if (!resizeArray(&words, &currentSize, 5))
      {
        fprintf(stderr, "Could not resize words\n");
        break;
      }
    }
    strcpy(words[i], data[i]);
  }

  printf("current array size: %lu\n", (unsigned long) currentSize);
  printf("copied %lu words\n", (unsigned long) i);

  for (j = 0; j < i; j++)
  {
    printf("wordlist[%lu] = \"%s\"\n", (unsigned long) j, words[j]);
  }

  free(words);

  return 0;
}

1
如果你打算学习C++,STL对于动态分配非常有用且易于使用。你可以使用std::vector..

我不明白。std::vector???我是一个在Windows上使用C编程的初学者。请详细解释一下。 - Rafay
如果你在使用C语言编程,那么就不要考虑STL了。请按照John Boker提供的链接进行操作。 - Mahesh

1
如果你的示例中的15是变量,请使用Ninefingers,John Boker或Muggen提供的答案之一。 如果1000是变量,请使用realloc:
words = malloc(1000 * sizeof(char*));
// ... read 1000 words
if (++num_words > 1000)
{
    char** more_words = realloc(words, 2000 * sizeof(char*));
    if (more_words) {printf("Too bad");}
    else {words = more_words;}
}

在我上面的代码中,常量2000是一种简化;你应该添加另一个变量capacity来支持超过2000个单词:
if (++num_words > capacity)
{
    // ... realloc
    ++capacity; // will reallocate 1000+ words each time; will be very slow
    // capacity += 1000; // less reallocations, some memory wasted
    // capacity *= 2; // less reallocations but more memory wasted
}

1
在现代C语言(C99)中,您有一个额外的选择,即变长数组(VLA),例如:
char myWord[N];

原则上,您也可以在二维中执行此操作,但如果您的尺寸过大,可能会出现堆栈溢出的风险。在您的情况下,最简单的方法是使用指向这样一个数组的指针,并使用malloc / realloc 来调整它们的大小:
typedef char Word[wordlen];
size_t m = 100000;

Word* words = malloc(m * sizeof(Word));
/* initialize words[0]... words[m-1] here */
for (size_t i = 0; i < m; ++i) words[i][0] = '\0';

/* array is too small? */
m *= 2;
void *p = realloc(words, m*sizeof(Word));
if (p) words = p;
else {
 /* error handling */
}
.
free(words);

如果wordlen是一个常量或变量,并且您将所有内容放在一个函数内,那么此代码应该可以正常工作(除了拼写错误)。如果您想要将其放置在一个函数中,您应该声明您的函数类似于:

void myWordFunc(size_t wordlen, size_t m, char words[m][wordlen]);

这意味着长度参数必须首先被知道,以便声明words


0

0
char ** words = malloc( 1000 * sizeof(char *));
int i;
for( i = 0 ; i < 1000 ; i++)
     *(words+i) = malloc(sizeof(char) * 15);

//....
for( i = 0 ; i < 1000 ; i++)
     free(*(words+i));

free(words);

1
你需要为char类型添加一个额外的内存位置,以便在末尾保留'\0'。是这样吗? - Mahesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接