Как это имеет смысл и почему функция sscanf все еще работает? - PullRequest
0 голосов
/ 26 августа 2018

Как видите, я выделил только 1 байт как sizeof(char) внутри цикла, и все же sscanf() читает весь блок вплоть до пробела в string_of_letters.Как это возможно?

Каково определение sscanf()?

Например: str = "rony is a man", но в string_of_letters позиции i Я вижу "rony".

char **string_of_letters;
int i;
char *read = str;

string_of_letters = (char**)malloc(3 * sizeof(char*));
for (i = 0; i < 3; i++) {
    string_of_letters[i] = (char*)malloc(sizeof(char));
    sscanf(read,"%[^, ]", &(*string_of_letters[i]));
    printf("%s\n", string_of_letters[i]);
}

Ответы [ 2 ]

0 голосов
/ 27 августа 2018

Есть много способов исправить показанный фрагмент кода.Этот код показывает три из них.Как отмечено в комментариях к вопросу, вам нужно выделить как минимум 2 символа внутри цикла (поскольку %[…] наборы сканирования создают строку с нулевым символом в конце), но затем вы можете использовать %1[^, ] в качестве преобразования, чтобы получить один символ ввремя.Обратите внимание, что вам нужно проверить возвращаемое значение sscanf(), чтобы убедиться, что вы получили то, что ожидали.Вам также нужно увеличить чтение, чтобы не читать один и тот же символ снова и снова.В более общих случаях вы должны использовать %n, чтобы узнать, где остановилось сканирование (см. Использование sscanf() в цикле ).Сканирующие наборы не пропускают пробелы (и не %c или %n - все другие стандартные преобразования не пропускают начальные пробелы, включая переводы строки).

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

enum { LIST_SIZE = 3 };

static void free_array(size_t n, char **arr)
{
    for (size_t i = 0; i < n; i++)
        free(arr[i]);
    free(arr);
}

int main(void)
{
    char str[] = "rony is a man";
    char **string_of_letters;
    char *read = str;

    printf("Variant 1:\n");
    string_of_letters = (char **)malloc(LIST_SIZE * sizeof(char *));
    for (int i = 0; i < LIST_SIZE; i++)
    {
        string_of_letters[i] = (char *)malloc(2 * sizeof(char));
        if (sscanf(&read[i], "%1[^, ]", string_of_letters[i]) != 1)
            printf("Conversion failed on %d\n", i);
        else
            printf("%s\n", string_of_letters[i]);
    }

    free_array(LIST_SIZE, string_of_letters);

    printf("Variant 2:\n");
    string_of_letters = (char **)malloc(LIST_SIZE * sizeof(char *));
    for (int i = 0; i < LIST_SIZE; i++)
    {
        string_of_letters[i] = (char *)malloc(sizeof(char));
        *string_of_letters[i] = read[i];
        printf("%c\n", *string_of_letters[i]);
    }

    free_array(LIST_SIZE, string_of_letters);

    printf("Variant 3:\n");
    strcpy(str, "  r o  n");

    char char_list[LIST_SIZE + 1];      // NB: + 1 provides space for null byte
    int offset = 0;
    for (int i = 0; i < LIST_SIZE; i++)
    {
        int pos;
        printf("Offset = %d: ", offset);
        if (sscanf(&read[offset], " %1[^, ]%n", &char_list[i], &pos) != 1)
        {
            printf("Conversion failed on character index %d\n", i);
            break;
        }
        else
            printf("%c\n", char_list[i]);
        offset += pos;
    }

    return 0;
}

Показанный код работает правильно при Valgrind на Mac под управлением MacOS 10.13.6 High Sierra с Valgrind 3.14.0.GIT (версия, извлеченная из Git, а не официально выпущенный набор исходного кода).

Вывод:

Variant 1:
r
o
n
Variant 2:
r
o
n
Variant 3:
Offset = 0: r
Offset = 3: o
Offset = 5: n

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

C11 §7.22.3 Функции управления памятью

…1… Указатель, возвращаемый в случае успешного выделения, соответствующим образом выравнивается, чтобы его можно было присвоить указателю на любой тип объекта с фундаментальным требованием выравнивания, а затем использовать для доступа к такому объекту или массиву таких объектовв отведенном месте (…).…

Это означает, что последовательные выделения одного char не будут смежными из-за требований выравнивания других типов.Как правило, вы обнаружите, что минимальное выделенное пространство составляет 8 или 16 байт (на 32-разрядных или 64-разрядных платформах), но это ни в коем случае не требуется.Это означает, что часто выделяется больше места, чем вы запрашивали (особенно если вы запрашиваете один байт).Однако доступ к этому дополнительному пространству приводит к неопределенному поведению.Выполнение вашего примера кода показывает, что иногда «неопределенное поведение» ведет себя более или менее ожидаемым образом.

0 голосов
/ 26 августа 2018

C не требует проверки границ памяти во время выполнения, поэтому тот факт, что вы выделили только один байт, не имеет значения для функции sscanf: он с радостью попытается сохранить всю строку в той области памяти, на которую указываетуказатель вы предоставляете.Однако если буфер недостаточно велик, результатом является неопределенное поведение, точные последствия которого зависят от слишком большого числа факторов, которые следует учитывать (используемый компилятор и его версия, операционная система, текущее состояние памяти и т. Д.).

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

...