如何为二维数组分配和释放堆内存?

15

我习惯使用PHP,但现在开始学习C语言。我正在尝试创建一个程序,逐行读取文件并将每一行存储到一个数组中。

到目前为止,我已经有了一个可以逐行读取文件的程序,甚至可以打印出每一行,但现在我只需要将每一行添加到一个数组中。

昨晚我的朋友告诉我一些关于这个问题的信息。他说我需要在C语言中使用多维数组,基本上是array[x][y][y]部分很容易,因为我知道每一行的最大字节数。然而,我不知道文件会有多少

我想我可以让它循环遍历文件,并每次增加一个整数并使用它,但我觉得可能有更简单的方法。

接下来我该尝试什么?


您可以使用函数realloc来在以后更改数组的大小。 - Jonathon
我会查找那个函数并思考如何实现它,然后再回复你,谢谢。 - Rob
6个回答

14
为动态分配一个二维数组:
```c int **arr; int rows = 5, cols = 10;
arr = malloc(rows * sizeof(int *)); for (int i = 0; i < rows; i++) { arr[i] = malloc(cols * sizeof(int)); } ```
char **p;
int i, dim1, dim2;


/* Allocate the first dimension, which is actually a pointer to pointer to char   */
p = malloc (sizeof (char *) * dim1);

/* Then allocate each of the pointers allocated in previous step arrays of pointer to chars
 * within each of these arrays are chars
 */
for (i = 0; i < dim1; i++)
  {
    *(p + i) = malloc (sizeof (char) * dim2);
   /* or p[i] =  malloc (sizeof (char) * dim2); */
  }

 /* Do work */

/* Deallocate the allocated array. Start deallocation from the lowest level.
 * that is in the reverse order of which we did the allocation
 */
for (i = 0; i < dim1; i++)
{
  free (p[i]);
}
free (p);

修改上面的方法。当需要添加另一行时,请执行*(p + i) = malloc (sizeof (char) * dim2);并更新i。在这种情况下,您需要预测文件中最大行数,该行数由变量dim1指示,我们首次分配p数组。这只会分配(sizeof(int *)*dim1)字节,因此比char p[dim1][dim2]更好。

我认为还有另一种方法。按块分配数组,并在溢出时链接它们。

struct _lines {
   char **line;
   int n;
   struct _lines *next;
} *file;

file = malloc (sizeof (struct _lines));
file->line = malloc (sizeof (char *) * LINE_MAX);
file->n = 0;
head = file;

完成此操作后,第一个块已准备好使用。当您需要插入一行时,请执行以下操作:

/* get line into buffer */
file.line[n] = malloc (sizeof (char) * (strlen (buffer) + 1));
n++;

n 等于 LINE_MAX 时,分配另一个块并将其链接到此块。

struct _lines *temp;

temp = malloc (sizeof (struct _lines));
temp->line = malloc (sizeof (char *) * LINE_MAX);
temp->n = 0;
file->next = temp;
file = file->next;

类似这样。

当一个块的 n 变成 0 后,释放它,并将当前块指针 file 更新为前一个块。您可以从单向链表的开头遍历或使用双向链接。


我认为后一种方法是GNU的tail(1)实现方式,当输入文件是不可寻址的(例如管道或stdin)时。由于在这些情况下它只能通过输入文件进行一次遍历,因此它将文件存储在内存块的链接列表中,并且当它到达文件末尾时,它会向后搜索以打印出最后的N行。 - Adam Rosenfield
@Adam Rosenfield:不知道这个,谢谢你提供的信息。我很久以前用它来存储一长串单词在内存中,非常有用。 - phoxis

7
在C语言中没有标准的可调整大小的数组类型。您需要自己实现它或使用第三方库。以下是一个简单的基本示例:
typedef struct int_array
{
    int *array;
    size_t length;
    size_t capacity;
} int_array;

void int_array_init(int_array *array)
{
    array->array = NULL;
    array->length = 0;
    array->capacity = 0;
}

void int_array_free(int_array *array)
{
    free(array->array);
    array->array = NULL;
    array->length = 0;
    array->capacity = 0;
}

void int_array_push_back(int_array *array, int value)
{
    if(array->length == array->capacity)
    {
        // Not enough space, reallocate.  Also, watch out for overflow.
        int new_capacity = array->capacity * 2;
        if(new_capacity > array->capacity && new_capacity < SIZE_T_MAX / sizeof(int))
        {
            int *new_array = realloc(array->array, new_capacity * sizeof(int));
            if(new_array != NULL)
            {
               array->array = new_array;
               array->capacity = new_capacity;
            }
            else
                ; // Handle out-of-memory
        }
        else
            ; // Handle overflow error
    }

    // Now that we have space, add the value to the array
    array->array[array->length] = value;
    array->length++;
}

使用方法如下:

int_array a;
int_array_init(&a);

int i;
for(i = 0; i < 10; i++)
    int_array_push_back(&a, i);
for(i = 0; i < a.length; i++)
    printf("a[%d] = %d\n", i, a.array[i]);

int_array_free(&a);

当然,这只适用于int数组。由于C语言没有模板,您需要为每种不同类型的数组编写宏代码(或使用其他预处理器,如GNU m4)。或者,您可以使用通用数组容器,该容器使用void*指针(要求所有数组元素都是malloc)或不透明的内存块,这将需要对每个元素访问进行强制类型转换,并且对每个元素的读写都需要使用memcpy
无论哪种情况,都不太美观。二维数组更加丑陋。

2

在这里,你可以使用链表而不是数组。虽然代码更简单,但是分配的次数更频繁,可能会遇到碎片化问题。

只要你不打算进行大量随机访问(这里是O(n)),迭代就像普通数组一样简单。

typedef struct Line Line;
struct Line{
    char text[LINE_MAX];
    Line *next;
};

Line *mkline()
{
    Line *l = malloc(sizeof(Line));
    if(!l)
       error();
    return l;
}

main()
{
    Line *lines = mkline();
    Line *lp = lines;
    while(fgets(lp->text, sizeof lp->text, stdin)!=NULL){
         lp->next = mkline();
         lp = lp->next;
    }
    lp->next = NULL;
}

1

虽然多维数组可以解决这个问题,但矩形2D数组并不是C语言的自然解决方案。

这里有一个程序,最初将文件读入链表中,然后分配一个正确大小的指针向量。每个单独的字符都会出现为array[line][col],但实际上每行只有需要的长度。它是C99,除了<err.h>

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

typedef struct strnode {
  char *s;
  struct strnode *next;
} strnode;

strnode *list_head;
strnode *list_last;

strnode *read1line(void) {
  char space[1024];
  if(fgets(space, sizeof space, stdin) == NULL)
    return NULL;
  strnode *node = malloc(sizeof(strnode));
  if(node && (node->s = malloc(strlen(space) + 1))) {
    strcpy(node->s, space);
    node->next = NULL;
    if (list_head == NULL)
      list_head = node;
    else
      list_last->next = node;
    list_last = node;
    return node;
  }
  err(1, NULL);
}

int main(int ac, char **av) {
  int n;
  strnode *s;

  for(n = 0; (s = read1line()) != NULL; ++n)
    continue;
  if(n > 0) {
    int i;
    strnode *b;
    char **a = malloc(n * sizeof(char *));
    printf("There were %d lines\n", n);
    for(b = list_head, i = 0; b; b = b->next, ++i)
      a[i] = b->s;
    printf("Near the middle is: %s", a[n / 2]);
  }
  return 0;
}

1

您可以使用mallocrealloc函数动态分配和调整指向char的指针数组,数组的每个元素将指向从文件中读取的字符串(该字符串的存储也是动态分配的)。为简单起见,我们假设每行的最大长度小于M个字符(包括换行符),因此我们不必对单个行进行任何动态调整大小。

每次扩展数组时,您需要手动跟踪数组大小。一种常见的技术是每次扩展时将数组大小加倍,而不是按固定大小扩展;这最小化了对realloc的调用次数,这可能是昂贵的。当然,这意味着您必须跟踪两个量;数组的总大小和当前读取的元素数量。

示例:

#define INITIAL_SIZE ... // some size large enough to cover most cases

char **loadFile(FILE *stream, size_t *linesRead)
{
  size_t arraySize = 0;   
  char **lines = NULL;
  char *nextLine = NULL;

  *linesRead = 0;

  lines = malloc(INITIAL_SIZE * sizeof *lines);
  if (!lines)
  {
    fprintf(stderr, "Could not allocate array\n");
    return NULL;
  }

  arraySize = INITIAL_SIZE;

  /**
   * Read the next input line from the stream.  We're abstracting this
   * out to keep the code simple.
   */
  while ((nextLine = getNextLine(stream)))  
  {
    if (arraySize <= *linesRead)
    {
      char **tmp = realloc(lines, arraysSize * 2 * sizeof *tmp);
      if (tmp)
      {
        lines = tmp;
        arraySize *= 2;
      }
    }
    lines[(*linesRead)++] = nextLine;
  )

  return lines;
}

1

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