Есть много способов исправить показанный фрагмент кода.Этот код показывает три из них.Как отмечено в комментариях к вопросу, вам нужно выделить как минимум 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-разрядных платформах), но это ни в коем случае не требуется.Это означает, что часто выделяется больше места, чем вы запрашивали (особенно если вы запрашиваете один байт).Однако доступ к этому дополнительному пространству приводит к неопределенному поведению.Выполнение вашего примера кода показывает, что иногда «неопределенное поведение» ведет себя более или менее ожидаемым образом.