Почему моя строковая функция не работает в C?

Это мой второй день на 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 

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

Компилятор предлагает вам добавить 1 к char *arr = (char* )malloc(sizeof(char) * strlen(piece));. Так и должно быть char *arr = malloc(strlen(piece)+1);: godbolt.org/z/Tj5n43a6s

mch 31.07.2024 10:41

«Это мой второй день на С». Со всем уважением предложите вам потратить больше времени на изучение хорошей вводной книги (и выполнение всех ее упражнений), прежде чем пытаться писать собственные программы. Там вы прочитали, что строки C требуют дополнительного байта для символа завершения NUL. Пожалуйста, учитесь самостоятельно и возвращайтесь, когда у вас уже есть основы. SO не является бесплатным учебным сервисом. (Я вижу, у вас есть некоторый опыт владения другими языками. Молодцы! Продолжайте учиться!)

Fe2O3 31.07.2024 10:43

Вы пропустили тот самый +1 в строке realloc: char *newarr = realloc(arr, startFrom + strlen(piece) + 1); Тогда всё работает нормально: godbolt.org/z/Yx8EM7aM7

mch 31.07.2024 10:45

«познакомиться с тем, как работает память». Одна вещь, о которой вы читали, это то, что malloc() и его родственники являются системными вызовами для резервирования фрагмента «кучей памяти». Правильно функционирующие программы ДОЛЖНЫ вызывать free(), когда эти фрагменты больше не нужны. Да, «зарезервированная» память кучи будет освобождена при выходе из программы, но... Некоторые программы работают в течение многих дней или месяцев без остановки и могут исчерпать доступную куча (и произойти сбой в 2 часа ночи). «Медленно и устойчиво» поможет вам пройти финишная черта. «Нетерпение» приводит к авариям недалеко от стартовых ворот.

Fe2O3 31.07.2024 10:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

короткий ответ:

  • Во-первых, это отсутствие +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 31.07.2024 10:55

@Jabberwocky Я этого не заметил, но это и другое, тогда у него 2 ошибки

Hamza AlAjlouni 31.07.2024 10:58

@Jabberwocky отредактировано после вашего комментария, спасибо вам

Hamza AlAjlouni 31.07.2024 12:37

@HamzaAlAjlouni Я как раз писал еще один вопрос о том, как это работает, я знал о нулевом терминаторе, поэтому начал с байта 6, но забыл сделать это в mallloc и realloc. Спасибо за ваши усилия и исчерпывающий ответ.

Melly 31.07.2024 14:51

@Мелли, пожалуйста, я рада, что помогла :)

Hamza AlAjlouni 31.07.2024 14:52

Другие вопросы по теме