Использование динамического выделения памяти для массивов

Как я должен использовать динамическое выделение памяти для массивов?

Например, вот следующий массив, в котором я читаю отдельные слова из файла .txt и сохраняю их слово за словом в массиве:

Код:

char words[1000][15];

Здесь 1000 определяет количество слов, которые может сохранить массив, и каждое слово может состоять не более чем из 15 символов.

Теперь я хочу, чтобы эта программа динамически выделяла память для количества слов, которые она считает. Например, файл .txt может содержать слов больше 1000. Теперь я хочу, чтобы программа подсчитала количество слов и соответственно распределила память.

Поскольку мы не можем использовать переменную вместо [1000], я совершенно не понимаю, как реализовать мою логику. Пожалуйста, помогите мне в этом отношении.


person Rafay    schedule 09.01.2011    source источник


Ответы (8)


Вы используете указатели.

В частности, вы используете указатель на адрес и, используя стандартные вызовы функций библиотеки 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 заканчиваются нулем, и это требует дополнительного символа, который вам нужно учитывать. Чтобы убедиться, что я это помню, я обычно устанавливаю размер переменной word_size таким, каким должен быть размер слова (длина строки, как я ожидаю) и явно оставляю +1 в malloc вместо нуля. Тогда я знаю, что выделенный буфер может принять строку из word_size символов. Не делать этого тоже нормально — я просто делаю это, потому что мне нравится явно учитывать ноль очевидным образом.

У этого подхода есть и обратная сторона — недавно я явно заметил, что это ошибка в доставке. Заметьте, я написал (word_size+1)*sizeof(type) — представьте, однако, что я написал word_size*sizeof(type)+1. Для sizeof(type)=1 это одно и то же, но Windows очень часто использует wchar_t — и в этом случае вы зарезервируете один байт для своего последнего нуля, а не два — и это элементы типа type, заканчивающиеся нулем, а не одиночные нулевые байты. Это означает, что вы будете перегружены чтением и записью.

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

person Community    schedule 09.01.2011
comment
Ваша терминология здесь кажется немного запутанной. Я ожидаю, что num_words == 2 подразумевает, что должно быть два слова, а слова [0] и слова [1] содержат их. Затем вы должны malloc(num_words * sizeof(char*)). - person Sam Brightman; 31.03.2015
comment
@ Сэм, ты прав. Я думаю, что имел в виду это в отношении +1 для учета нулевого терминатора. Исправление :) - person ; 31.03.2015
comment
Откуда взялась переменная num_words? - person atw; 01.12.2016

В то время как 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);
person Heatsink    schedule 09.01.2011
comment
Означает ли эта переменная num_words, которую я вижу повсюду, что мы должны указать длину массива и что мы просто выделяем память каждому элементу через malloc? Мы не увеличиваем размер массива динамически только для каждого элемента? - person atw; 01.12.2016

Если вы собираетесь перейти на C++, STL очень полезен для чего-то динамического распределения и очень прост. Вы можете использовать std::vector ..

person Mahesh    schedule 09.01.2011
comment
Я не понял. станд::вектор??? Я новичок в программировании на C в Windows. Пожалуйста, объясните мне немного дальше. - person Rafay; 09.01.2011
comment
Тогда забудьте о STL, если вы программируете на C. Перейдите по ссылке, указанной Джоном Бокером. - person Mahesh; 09.01.2011

Если 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
}
person anatolyg    schedule 09.01.2011

Если вы работаете в 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;
}
person John Bode    schedule 09.01.2011

Вот небольшая информация о динамическом размещении 2d-массивов:

http://www.eskimo.com/~scs/cclass/int/sx9b.html

person John Boker    schedule 09.01.2011

В современном 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.

person Jens Gustedt    schedule 09.01.2011

person    schedule
comment
Вам нужно добавить дополнительную ячейку памяти в char, чтобы сохранить «\ 0» в конце. не так ли? - person Mahesh; 09.01.2011