Как мне выделить память и определить размеры динамически в C? - PullRequest
3 голосов
/ 14 июля 2010

Я пытаюсь научить себя C на фоне питона. Моя текущая мини-проблема состоит в том, чтобы попытаться сделать менее жесткое кодирование таких вещей, как длины массивов, и динамически распределять память на основе ввода.

Я написал следующую программу. Я надеялся на предложения сообщества изменить его следующим образом:

1.) Создайте элементы first и last Name переменной длины. В настоящее время их длина закодирована как MAX_NAME_LENGTH. Это будет включать как изменение Name s struct декларации, так и способ, которым я присваиваю значения его элементам.

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;
}

Ответы [ 5 ]

5 голосов
/ 14 июля 2010

Очень простой подход - вообще не использовать массив, а скорее связанный список:

Это можно сделать несколькими способами, но простейшим, вероятно, является изменение структуры имени следующим образом:

typedef struct _Name Name;

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

Использование char * вместо char [] потребует некоторого strcpy, но это действительно ни здесь, ни там. Чтобы расширить массив, вы можете просто распределить эти элементы по одному; и установите следующее соответственно.

Примечание: Не забудьте установить рядом с NULL при создании новых элементов хвоста.

4 голосов
/ 14 июля 2010

Нет способа расширить массив в C. Все, что вы можете сделать, - это выделить больший блок памяти и скопировать элементы. Это то, что Python делает под прикрытием.

Также нет способа определить размер массива, поскольку он хранит только элементы без дополнительной информации (массивы Python хранят длину вместе с элементами.) Вы можете поставить маркер в конце массива и считать элементы пока вы не нажмете маркер, так работают строки (нулевой символ '\ 0' - маркер.)

2 голосов
/ 14 июля 2010

Вам понадобится массив максимальной длины для ввода - вы не знаете, как долго будет работать ввод. Однако вам нужен только один массив этой длины. Получив входные данные, вы можете получить его длину и выделить массив только нужного размера для хранения имен.

Что касается расширения вашего списка, вы можете использовать realloc или использовать связанный список, конечно.

1 голос
/ 15 июля 2010

Поскольку никто еще не упомянул об этом, если вы используете * scanf для чтения ненадежных входных строк, вы всегда должны использовать спецификаторы максимальной ширины поля .Как и

scanf("%19s", str);

Обратите внимание, что здесь указывается максимальная длина строки без учета завершающего NUL, поэтому в этом примере str должен иметь длину 20 символов.

Если вы не ограничиваете входные преобразования scanfтаким образом, при переполнении буфера вы получите не только неопределенное поведение, но и уязвимость безопасности.

Возвращаясь к вашему вопросу о динамически растущих буферах.Представьте, что вы читаете некоторый ввод, состоящий из строк, и каждая строка может иметь максимальную ширину в 78 символов.В подобных ситуациях вы знаете максимальную длину каждой строки, но не имеете представления о максимальной длине вашего ввода в целом.Обычный способ сделать это - выделить некоторое пространство по умолчанию, и если вам нужно увеличить это пространство, увеличьте его на * 2, чтобы вам не приходилось много раз использовать realloc ().

1 голос
/ 14 июля 2010

Вы начали читать о связанных списках? Это может помочь вам иметь динамическую структуру.

Также вы можете проверить разницу между массивом и связанным списком. Указатель на символ может быть членом вашего связанного списка, который может выделяться динамически.

Проверьте эту ссылку от stackoverflow для получения дополнительной информации об этом.

...