2015-12-12 3 views
1

У меня есть следующий код, который может динамически выделять один sentece:Динамический 2D массив символов с помощью таНос

int size=1; 
char * text = (char*) malloc(size * sizeof(char)); 

size = (int)sizeof(text); 

fgets(text, si, stdin); 

//remove new line() 

printf ("Sentence = <%s>\n", text); 

Я хотел бы иметь возможность выделять и хранить несколько строк, заканчивающиеся в «\ п» для дальнейшей работы с (формат), я не знаю, сколько строк я буду выделять или как долго они будут. Ввод строк заканчивается EOF. Это не должно быть с fgets. Например:

  • SENTENCE1 \ п
  • SENTENCE2 \ п .... и т.д.

Любые идеи?

+0

Использование символа char ** объявляет указатель на указатели на символы. Тогда у вас будет массив строк. –

+0

@Supercan Array не является указателем ... или вы имеете в виду массив указателей? – Michi

+0

@ChuanzhenWu В 'C', или' array of strings' нет такой строки, как 'strings'. – Michi

ответ

3

Это классический вопрос о том, как обрабатывать динамическое распределение и перераспределение для хранения неизвестного количества строк. Стоит понять этот процесс подробно, поскольку он послужит основой для любого любого другого обстоятельства, когда вы читаете неизвестное количество значений (будь то структуры, поплавки, символы и т. Д.).

Есть целый ряд различных типов структур данных, которые вы можете использовать, списки, деревья и т.д., но основной путь (как вы это называете «2D массив символов») обрабатывается путем создания массива pointer-to-pointer-to-typeтип, являющийся в этом случае char), а затем распределение пространства для заполнения и заполнения стартового адреса для нового блока памяти каждому указателю, когда ваши данные считываются , Стенография для указателя на указатель к типу просто двойного указатель (например char **array;, который является технически указатель на указатель к полукоксу или указатель на символ *, если хотите)

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

При назначении ваших указателей изначально и в качестве части вашего перераспределения вы можете воспользоваться, указав каждый указатель на NULL. Это легко выполнить для первоначального распределения. Просто используйте calloc вместо malloc. При перераспределении требуется, чтобы вы установили все новые указатели, выделенные для NULL.

Почему? Это не обязательно, но это позволяет вам перебирать массив указателей, не зная количества строк. Как это работает? Например, предположим, что вы инициализировали 100 указателей на NULL и назначили количество строк каждому указателю, когда идете.Чтобы перебрать коллекцию, вы можете просто:

size_t i = 0; 
while (array[i]) { 
    ... do your stuff ... 
} 

Только указатели, которые вы присвоили что-то, будут иметь значение. Таким образом, цикл будет только взаимодействовать над указателями, которые имеют значение, останавливаясь при обнаружении первого указателя NULL. (первый NULL просто служит вашим доцентом значение, сообщающее вам, когда остановиться). Это также позволяет передавать указатель на вашу коллекцию на любую функцию, не передавая также количество строк/значений. (примечание: нет оснований не передавать размер коллекции, но есть обстоятельства, когда это преимущество)

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

То же самое относится к распределению хранилища для ваших линий. Если вы используете calloc вместо malloc, вы инициализируете все значения до 0 (nul). Затем хранилище для всех ваших строк будет nul-terminated в силу инициализации. То же самое относится и к распределению для числовых массивов. Путем инициализации всех значений до 0 вы предотвращаете любую возможность случайного попыток чтения из неинициализированного значения (неопределенное поведение). Хотя обычно это не проблема при последовательном заполнении/чтении из массива, когда используются рандомизированные процедуры хранения и поиска, это может быть реальной проблемой.

При выделении памяти, вы должны подтвердить, что каждый вызов удалось (например, для malloc, calloc, realloc, а также для других вызовов функций, которые выделяют для вас, как strdup). Это всего лишь простая проверка, но она может привыкнуть делать каждый раз или рисковать попытками чтения и записи из/в нераспределенную память. В приведенном ниже примере простые функции используются в качестве обертки для calloc и realloc, которые обеспечивают необходимые проверки. Хотя нет необходимости использовать подобные вспомогательные функции, они помогают сохранить основную часть вашего кода без повторных проверок памяти и т. Д., Что может затруднить чтение кода.

Заключительная записка по realloc. Всегда используйте временную переменную для возврата realloc. Зачем? При успешном завершении realloc возвращает указатель на вновь выделенный блок памяти. При ошибке он возвращает NULL. Если вы не используете временный указатель и запрос не работает, вы утратили доступ к (потерянному адресу) всех ваших ранее сохраненных значений. После того, как вы подтвердите успешное выполнение realloc, просто назначьте свой временный указатель оригиналу.

В приведенном ниже примере будут прочитаны все строки из имени файла, указанного в качестве первого аргумента программы (или stdin по умолчанию). Он использует fgets для чтения каждой строки. Каждая строка проверяется, чтобы убедиться, что все символы в строке были успешно прочитаны. Если линия была слишком длинной, чтобы вписаться в указанное пространство, дается простое предупреждение, а остальная часть считывается в следующую строку хранения (вы можете realloc и соединить здесь также). Все строки хранятся в array. Есть MAXL (64) указатели изначально выделены, и каждая строка может содержать MAXC (256) символов. Вы можете изменить либо в соответствии с вашими потребностями, либо установить MAXL на номер 1, чтобы начать перераспределение строк. Строки просто печатаются на терминале, а затем вся память освобождается до выхода программы.

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

#define MAXC 256 /* max chars per-line */ 
#define MAXL 64 /* initial num lines */ 

void *xcalloc (size_t n, size_t s); 
void *xrealloc_dp (void *ptr, size_t *n); 

int main (int argc, char **argv) { 

    char **array = NULL; 
    char buf[MAXC] = {0}; 
    size_t i, idx = 0, maxl = MAXL; 
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin; 

    if (!fp) { 
     fprintf (stderr, "error: file open failed '%s'.\n", argv[1]); 
     return 1; 
    } 

    array = xcalloc (maxl, sizeof *array); /* allocate maxl pointers */ 

    while (fgets (buf, MAXC, fp)) /* read all lines from fp into array */ 
    { 
     size_t len = strlen (buf); 

     /* validate complete line read */ 
     if (len + 1 == MAXC && buf[len - 1] != '\n') 
      fprintf (stderr, "warning: line[%zu] exceeded '%d' chars.\n", 
        idx, MAXC); 

     /* strip trailing '\r', '\n' */ 
     while (len && (buf[len-1] == '\n' || buf[len-1] == '\r')) 
      buf[--len] = 0; 

     /* allocate & copy buf to array[idx], nul-terminate 
     * note: this can all be done with array[idx++] = strdup (buf); 
     */ 
     array[idx] = xcalloc (len + 1, sizeof **array); 
     strncpy (array[idx], buf, len); 
     array[idx++][len] = 0; 

     /* realloc as required (note: maxl passed as pointer) */ 
     if (idx == maxl) array = xrealloc_dp (array, &maxl); 
    } 
    if (fp != stdin) fclose (fp); 

    printf ("\n lines read from '%s'\n\n", argc > 1 ? argv[1] : "stdin"); 
    for (i = 0; i < idx; i++) 
     printf (" line[%3zu] %s\n", i, array[i]); 

    for (i = 0; i < idx; i++) 
     free (array[i]); /* free each line */ 
    free (array);   /* free pointers */ 

    return 0; 
} 

/* simple calloc with error checking */ 
void *xcalloc (size_t n, size_t s) 
{ 
    void *memptr = calloc (n, s); 
    if (memptr == 0) { 
     fprintf (stderr, "xcalloc() error: virtual memory exhausted.\n"); 
     exit (EXIT_FAILURE); 
    } 

    return memptr; 
} 

/* realloc array of pointers ('memptr') to twice current 
* number of pointer ('*nptrs'). Note: 'nptrs' is a pointer 
* to the current number so that its updated value is preserved. 
* no pointer size is required as it is known (simply the size 
* of a pointer 
*/ 
void *xrealloc_dp (void *ptr, size_t *n) 
{ 
    void **p = ptr; 
    void *tmp = realloc (p, 2 * *n * sizeof tmp); 
    if (!tmp) { 
     fprintf (stderr, "xrealloc_dp() error: virtual memory exhausted.\n"); 
     exit (EXIT_FAILURE); 
    } 
    p = tmp; 
    memset (p + *n, 0, *n * sizeof tmp); /* set new pointers NULL */ 
    *n *= 2; 

    return p; 
} 

Compile

gcc -Wall -Wextra -O3 -o bin/fgets_lines_dyn fgets_lines_dyn.c 

Использование/выход

$ ./bin/fgets_lines_dyn dat/captnjack.txt 

lines read from 'dat/captnjack.txt' 

    line[ 0] This is a tale 
    line[ 1] Of Captain Jack Sparrow 
    line[ 2] A Pirate So Brave 
    line[ 3] On the Seven Seas. 

Memory Leak/Проверка ошибок

В любом кода у мы пишем, что динамически выделяет память, у вас есть 2 ответа относительно любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока памяти, поэтому (2) он может быть освобожден, когда он больше не нужен. Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не писали за пределами вашего выделенного блока памяти и не подтверждали, что освободили всю выделенную память. Для Linux valgrind это обычный выбор. Есть так много тонких способов злоупотребления блоком памяти, который может вызвать реальные проблемы, нет оправдания, чтобы не делать этого. Для каждой платформы есть аналогичные проверки памяти. Все они просты в использовании. Просто запустите свою программу.

$ valgrind ./bin/fgets_lines_dyn dat/captnjack.txt 
==22770== Memcheck, a memory error detector 
==22770== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. 
==22770== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info 
==22770== Command: ./bin/fgets_lines_dyn dat/captnjack.txt 
==22770== 

lines read from 'dat/captnjack.txt' 

    line[ 0] This is a tale 
    line[ 1] Of Captain Jack Sparrow 
    line[ 2] A Pirate So Brave 
    line[ 3] On the Seven Seas. 
==22770== 
==22770== HEAP SUMMARY: 
==22770==  in use at exit: 0 bytes in 0 blocks 
==22770== total heap usage: 6 allocs, 6 frees, 1,156 bytes allocated 
==22770== 
==22770== All heap blocks were freed -- no leaks are possible 
==22770== 
==22770== For counts of detected and suppressed errors, rerun with: -v 
==22770== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2) 

Просто ищите Все блоки кучи были освобождены - отсутствие протечек можно и ОШИБКИ РЕЗЮМЕ: 0 ошибок от 0 контекстов. Если у вас их нет, вернитесь и выясните, почему.

Это закончилось намного дольше, чем ожидалось, но это то, что стоит понять. Сообщите мне, есть ли что-нибудь еще, на что у вас есть вопросы.

+0

Вау, это очень всеобъемлющий ответ. Некоторые замечания: рост ('realloc()' ating) на постоянной величине - плохая идея, так как она будет иметь квадратичную сложность вместо амортизированной линейной. Выращивание любым постоянным * фактором * больше 1 является хорошим, жестким. Вы можете использовать 3/2, если 2 кажется слишком большим. Я не уверен, что согласен с рекомендацией нулевой инициализации. Если вы все равно будете читать байты в массиве, я думаю, что заполнение его нулями до этого просто пустая трата времени. Если доступно, ['getline'] (http://linux.die.net/man/3/getline) будет очень полезно. – 5gon12eder

+0

Просто любопытно: что вам не нравилось, когда я снимал синтаксис синтаксиса из текста, который не был исходным кодом C? – 5gon12eder

+0

Это, должно быть, попало в одно из моих правлений. Я редактировал, и он попросил меня вернуться, и я нажал «да». Сожалею. Это не было преднамеренным. И да, есть '+/-' 's в вопросе' calloc', 'malloc'. Это политический момент. Я тестировал более 100 000 000 итераций 'malloc' verses' calloc', и разница незначительна'.Я использую смесь либо, когда пишу, но и для ответов, я хотел бы отметить преимущества обнуления. Тогда ОП может выяснить, в какой лагерь он попадает. –

Смежные вопросы