Это мой второй день на C. Я все еще пытаюсь ознакомиться с тем, как работает память! Вот мой код, который я создал для разделения строк на массив, он неплохо справляется со своей задачей:
char* stringCutterOneDArr(char *str, int *len){
if (strlen(str) == 0) {
printf("No string is inputted.\n");
return "";
}
char *piece = strtok(str, " ");
int startFrom = 0;
int arrLen = 0;
char *arr = (char* )malloc(sizeof(char) * strlen(piece));
if (arr == NULL) {
printf("Memory allocation failed.");
return "";
}
/* I'll comment this later for the example */
strcpy(arr, piece);
startFrom += strlen(piece) + 1;
arrLen++;
piece = strtok(NULL, " ");
while(piece != NULL) {
// Reallocate memory before assigning string.
char *newarr = (char* )realloc(arr, startFrom + sizeof(char) * strlen(piece));
if (newarr == NULL) {
printf("Memory allocation failed.");
return "";
}
arr = newarr;
// Assing the string to the newly allocated memory
strcpy(arr + startFrom, piece);
startFrom += strlen(piece) + 1;
arrLen++;
// Refetch the next change if there's any
piece = strtok(NULL, " ");
}
*len = arrLen;
return arr;
}
void stringSplitOneDArr() {
char str[] = "Let's break this string apart";
int stringStart = 0;
int len;
char *string = stringCutterOneDArr(str, &len);
for(int i = 0; i < len; i++) {
char *selectedString = string + stringStart;
printf("%s\n", selectedString);
stringStart += strlen(selectedString) + 1;
}
}
Этот созданный код выдаст ожидаемый результат:
❯ runc
Let's
break
this
string
apart
Однако, если я прокомментировал первый strcpy
, выделенный выше, он напечатает \n
только пять раз (что указывает на то, что массив пуст). Ожидаемый результат для меня, когда я прокомментировал первый strcpy
, был:
❯ runc
break
this
string
apart
Потому что он пропускает первый, хотя память для первого уже выделена. А затем продолжайте печатать остальное, как обычно. Вместо этого ни одна из пяти строк не находится в массиве. Почему это случилось?
«Это мой второй день на С». Со всем уважением предложите вам потратить больше времени на изучение хорошей вводной книги (и выполнение всех ее упражнений), прежде чем пытаться писать собственные программы. Там вы прочитали, что строки C требуют дополнительного байта для символа завершения NUL. Пожалуйста, учитесь самостоятельно и возвращайтесь, когда у вас уже есть основы. SO не является бесплатным учебным сервисом. (Я вижу, у вас есть некоторый опыт владения другими языками. Молодцы! Продолжайте учиться!)
Вы пропустили тот самый +1 в строке realloc
: char *newarr = realloc(arr, startFrom + strlen(piece) + 1);
Тогда всё работает нормально: godbolt.org/z/Yx8EM7aM7
«познакомиться с тем, как работает память». Одна вещь, о которой вы читали, это то, что malloc()
и его родственники являются системными вызовами для резервирования фрагмента «кучей памяти». Правильно функционирующие программы ДОЛЖНЫ вызывать free()
, когда эти фрагменты больше не нужны. Да, «зарезервированная» память кучи будет освобождена при выходе из программы, но... Некоторые программы работают в течение многих дней или месяцев без остановки и могут исчерпать доступную куча (и произойти сбой в 2 часа ночи). «Медленно и устойчиво» поможет вам пройти финишная черта. «Нетерпение» приводит к авариям недалеко от стартовых ворот.
короткий ответ:
+1
в malloc(sizeof(char) * strlen(piece))
и realloc(arr, startFrom + strlen(piece));
.strcpy(arr + startFrom, piece);
добавляет
дополнительные токены к arr
. но если пропустить strcpy(arr, piece);
,
arr
содержит случайные данные или просто не инициализирован. затем добавить будет
приводят к неопределенному поведению, часто приводящему к пустым строкам или мусору
значения при печати строк позже.объяснение:
Я подробно объясню вам проблему и то, как ваш код работал в первую очередь, потому что мне пришло в голову, как он вообще работал правильно? это даже не должно работать, поэтому давайте разберем это:
+1
в
malloc(sizeof(char) * strlen(piece))
и realloc(arr, startFrom + strlen(piece));
вы резервируете место в памяти для своей строки, но в то же время забыли выделить место для терминатора null
(\0)
. Это может привести к неправильному завершению строки. это означает, что завершение строки не определено, тогда вы печатаете следующим образом:
printf("%s\n", selectedString);
Итак, вы ищете (/0)
, чтобы напечатать первый токен, второй и т. д., но вопрос в следующем:
Как это вообще работало без +1? как до комментария все работало правильно?
ответ на этот вопрос: повезло, поведение было таким же, и позвольте мне показать вам, что произошло
сначала вы сделали malloc(sizeof(char) * strlen(piece))
для размера 5, что точно "let's"
без (/0)
, затем вы скопировали следующим образом: strcpy(arr, piece);
, который представляет собой копию памяти, которая переносит точные данные памяти в другое место памяти.
итак в результате: вы скопировали "let's/0"
в arr
и совершили нарушение памяти, так как превысили размер, вы скопировали 6 байт в выделенную память размером 5 байт.
обратитесь к этой фотографии здесь:
затем вы выполнили перераспределение, чтобы добавить следующий токен "break"
, но вы начали перераспределение с байта номер 6, а не с 5, что означает, что вы добавили байт после "let's"
, а затем вы снова выполнили копирование памяти с копированием дополнительного байта, что привело к нарушению памяти. Посмотри на фото:
Итак, как вы видите, (/0)
после "let's"
был потерян, потому что он уже не был зарезервирован. и вы делали то же самое для каждого токена, и в результате у токена был один неопределенный байт.
последнее arr
, которое вы возвращаете из функции, таково:
(4c 65 74 27 73) cd (62 72 65 61 6b) cd (74 68 69 73)
cd (73 74 72 69 6e 67) cd (61 70 61 72 74) 00
каждый (cd)
— это неопределенный байт, а (00)
в конце — нарушение памяти. вы можете потерять его, если другая программа возьмет этот байт.
затем в конце использование printf("%s\n", selectedString);
по счастливой случайности перерезает веревку на (cd)
, и, по счастливой случайности, (00)
все еще был доступен в памяти.
так что это отвечает на вопрос, как это работало.
как он перестал работать после комментария strcpy?
Пропуск strcpy(arr, piece);
оставляет arr
со случайными или неинициализированными данными (5 bytes of (cd)
). это означает 5 пустых строк согласно printf("%s\n", selectedString);
чтобы доказать, что измените цикл с i < len
на i < 10
, и вы увидите 5 пустых строк, а затем ваши строки.
Короче говоря, strcpy(arr, piece);
инициализирует arr
, а сохранение arr
неопределенным приведет к полученному вами поведению. плюс недостающее распределение +1.
А как насчет отсутствующего +1
в malloc(sizeof(char) * strlen(piece))
? И в любом случае пропуск strcpy(arr, piece);
невозможен. Этот ответ совершенно неверен.
@Jabberwocky Я этого не заметил, но это и другое, тогда у него 2 ошибки
@Jabberwocky отредактировано после вашего комментария, спасибо вам
@HamzaAlAjlouni Я как раз писал еще один вопрос о том, как это работает, я знал о нулевом терминаторе, поэтому начал с байта 6, но забыл сделать это в mallloc
и realloc
. Спасибо за ваши усилия и исчерпывающий ответ.
@Мелли, пожалуйста, я рада, что помогла :)
Компилятор предлагает вам добавить 1 к
char *arr = (char* )malloc(sizeof(char) * strlen(piece));
. Так и должно бытьchar *arr = malloc(strlen(piece)+1);
: godbolt.org/z/Tj5n43a6s