Как распределить память в C для каждого символа одновременно для двойных указателей char **

Мой вопрос заключается в том, как выделить память для каждого символа слова в то время, когда мы используем char **. Я пытаюсь сделать это так

words = malloc(sizeof(char*) * numberOfWords); 

и когда я готов скопировать символы:

 int i = 0; while(some condition){ (*words)[i] = malloc(1); //here I am getting error "Incompatible pointer to integer conversion assigning to char from void*" arrayOfWords++; i++; } 

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

Заранее спасибо!

Это стало немного дольше, чем ожидалось, но теперь это обещанный пример того, как распределить список слов. Чтобы упростить пример, предположим, что мы хотим разделить строку на пробел. Учитывая NUL символьную строку NUL , мы хотим создать NULL завершенный массив NUL символьных строк, представляющих отдельные слова. Никакая память не должна быть потрачена впустую.

Чтобы дать полный рабочий пример (просто конкатенировать следующие блоки кода), вот те заголовки, которые нам понадобятся:

 #include  #include  #include  #include  #include  

Мы собираемся выделить некоторую память, поэтому нам лучше подумать о том, чтобы освободить ее правильно. Начнем с того, что:

 void free_words(char * * words) { /* Treat NULL gracefully for consistency with the standard libary's free(). */ if (words != NULL) { size_t i; for (i = 0; words[i] != NULL; ++i) free(words[i]); free(words); } } 

Обратите внимание, что мы требуем, чтобы массив слов был NULL завершен. В противном случае у нас не было бы шанса сказать, где это закончится.

Теперь к токенизатору. Мы могли бы дважды перебрать строку и подсчитать, сколько слов у нее есть заранее. Но в целом это может быть невозможно, например, если строка фактически является непрерывным входным streamом. Я также хотел показать, как массив может расти динамически. Если выращивать массив, мы должны всегда увеличивать его размер с помощью мультипликативного фактора (обычно 2) для поддержания линейной амортизированной асимптотической сложности. (Если вы не знаете, что означает «линейная амортизированная асимптотическая сложность», просто используйте рекомендованную процедуру как наилучшую практику.)

 char * * tokenize(const char *const sentence) { size_t capacity = 1; size_t word_count = 0; ssize_t word_start = -1; size_t i = 0; char * * words = malloc(capacity * sizeof(char *)); char * * temp; if (words == NULL) goto fail; words[word_count] = NULL; do { if (isspace(sentence[i]) || sentence[i] == '\0') { if (word_start >= 0) { /* We have found the end of the current word. */ const size_t word_length = i - word_start; char * word; if (word_count + 1 >= capacity) { /* We need to grow the array. */ capacity *= 2; temp = realloc(words, capacity * sizeof(char *)); if (temp == NULL) goto fail; words = temp; } word = malloc((word_length + 1) * sizeof(char)); if (word == NULL) goto fail; strncpy(word, sentence + word_start, word_length); word[word_length] = '\0'; words[word_count++] = word; words[word_count] = NULL; word_start = -1; } } else { if (word_start < 0) { /* We have found the begin of a new word. */ word_start = i; } } } while (sentence[i++]); /* Trim the array to the exact size needed. */ temp = realloc(words, (word_count + 1) * sizeof(char *)); if (temp == NULL) goto fail; words = temp; return words; fail: free_words(words); return NULL; } 

Логика на самом деле найти границы слов довольно проста, но я не буду объяснять это здесь, так как это не имеет ничего общего с вопросом, который касается управления памятью.

Всякий раз, когда мы находим конец нового слова, мы проверяем, достаточно ли массива для его хранения и роста, если это необходимо. Убедившись, что массив достаточно большой, мы выделяем достаточно памяти для хранения следующего слова, копирования данных и вставки его в массив. Нам также необходимо позаботиться о завершении символьной строки с помощью NUL байта и массива слов с помощью указателя NULL .

Мы отслеживаем текущую емкость массива в переменной capacity и количество слов, вставленных до сих пор в переменную word_count . Когда мы решаем вырастить массив, мы используем функцию realloc которая пытается отрегулировать объем пространства, зарезервированного для этого указателя, и - если это невозможно - выделяет новое пространство, копирует данные и освобождает старый.

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

В любой момент, когда мы выделяем память, мы должны быть готовы обработать условие нехватки памяти. Стандартные функции библиотеки сообщают об этом, возвращая NULL . Если у нас заканчивается память, мы не выполняем операцию, также возвращая NULL . Однако мы не должны этого делать, не выпустив ранее выделенную память. Вы можете оскорбить мое использование goto s для этой цели, но, оказывается, это довольно распространенное и общепринятое использование этой языковой функции. (Используемый как это, он просто имитирует функциональные исключения, которые дали бы нам, если бы у них были C).

См. Man-страницы malloc , realloc и free для их точной семантики. Это довольно серьезный совет: я только что видел слишком много кода, который злоупотребляет ими, особенно в случае с углами.

Чтобы закончить пример, вот как можно использовать нашу функцию:

 int main() { const char sentence[] = "The quick brown fox jumps over the sleazy dog."; char * * words = tokenize(sentence); size_t i; if (words == NULL) return EXIT_FAILURE; for (i = 0; words[i] != NULL; ++i) { printf("words[%ld] = '%s'\n", i, words[i]); } free_words(words); return EXIT_SUCCESS; } 

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

Проблемы, которые я вижу:

Линия:

 *words = malloc(sizeof(char*) * numberOfWords); 

должно быть:

 words = malloc(sizeof(char*) * numberOfWords); 

Линия:

 (*words)[i] = malloc(1); 

должно быть:

 words[i] = malloc(1); 

Тип words[i]char* . Тип (*words)[i]char . Компилятор жалуется на назначение указателя (возвращаемого значения malloc ) на char .

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

Одна из страtagsй состоит в том, чтобы сначала выделить фиксированный блок памяти и постепенно добавлять к нему данные (в этом случае символы). После заполнения выделенной памяти вам необходимо выделить новый, более крупный блок памяти (часто в два раза больше размера исходного блока) и скопировать данные в новый блок памяти.

Обратите внимание, что эта страtagsя не увеличивает размер выделенной памяти по одному байту за раз. Это будет невероятно неэффективно.

Это более или менее то, что стандартные структуры данных делают на других языках и / или библиотеках (например, std::string и std::vector в C ++ и ArrayList на Java).

 #include  #include  #include  char **copy_words(char *src_words[], int numberOfWords){ char **words = malloc(sizeof(char*) * numberOfWords); int i = 0; while(i < numberOfWords){ words[i] = malloc(strlen(src_words[i]) + 1); strcpy(words[i], src_words[i]); i++; } return words; } int main(void){ char *words[] = { "first", "second", "..end"}; int numOfWords = sizeof(words)/sizeof(*words); char **clone = copy_words(words, numOfWords); int i; for(i = 0; i < numOfWords; ++i){ printf("%s\n", clone[i]); free(clone[i]); } free(clone); return 0; } 

Вы можете сделать вечно растущую строку довольно легкой …

  #define STRING_CHUNK_SIZE 100 typedef struct { char* s; unsigned int size; unsigned int allocated_size; } string; void string_create(string* s) { s->s = malloc(STRING_CHUNK_SIZE); s->s[0] = 0; s->size = 0; s->allocated_size = STRING_CHUNK_SIZE; } void string_add(string* s, char* str) { int len = strlen(str); if(s->size + len + 1 >= s->allocated_size) { int room = s->allocated_size - s->size; int needed = len+1-room; int togrow = needed / STRING_CHUNK_SIZE; if(needed % STRING_CHUNK_SIZE) togrow += STRING_CHUNK_SIZE; s->allocated_size += togrow; s->s = realloc(s->s, s->allocated_size); } s->size += len; strcat(s->s, str); } char* string_p(string* s) { return s->s; } void string_destroy(string* s) { free(s->s); } 

вы можете использовать что-то вроде

 int i; string s; string_create(&s); string_add(&s, "blah"); printf("%s\r\n", string_p(&s)); for(i = 0; i<100; i++) { string_add(&s, "blah"); } printf("%s\r\n", string_p(&s)); string_destory(&s); 

Если вы ДЕЙСТВИТЕЛЬНО ДЕЙСТВИТЕЛЬНО хотите выделение по 1 байт за раз, измените размер #define chunk на 1