C строки ведут себя так странно - PullRequest
0 голосов
/ 06 февраля 2019

Первый вопрос о нулевом символе \0 в конце строки, существует так много вариантов, когда \0 требуется / добавляется автоматически.При объявлении массива char мне нужно указывать \0 или нет?или в каком случае я должен указать \0, а в каком случае нет?Может кто-нибудь дать исчерпывающее резюме?(как в этом посте ).если вы чувствуете, что мой вопрос двусмысленный, то более конкретный вопрос - когда объявлять строку в C, что лучше всего, это char string[] = "first string", потому что, например, таким образом я могу сделать strcat(string, another_string), не беспокоясь опроблема размера?

Второй вопрос: у меня

1   char a[] = "kenny";
2   char b[3];
3   strncpy(b, a, (int)(sizeof(b) - 1));
4   printf("%i\n", (int)sizeof(b)); // 3
5   printf("string length: %i\n", (int)strlen(b)); // string length: 8
6   printf("%s\n", b); // give me random stuff like kekenny or keEkenny 
  • 3: я хочу передать только 2 байта в b
  • 4: sizeof ведет себя нормально
  • 5: но почему он становится 8 ???
  • 6: почему он дает мне случайные вещи, такие как kekenny или keEkenny

Я только что заблудилсяпроисходит в строке C.Раньше я часто использовал C ++, но до сих пор не могу понять, как ведет себя строка C.

Ответы [ 5 ]

0 голосов
/ 06 февраля 2019

Первый вопрос

Когда вы выполните

char string[] = "first string";
            ^
            No size specified

, компилятор зарезервирует память, в которой может содержаться именно текст «первая строка» и завершение NUL.Если вы напечатаете размер строки, вы получите 13. Другими словами - переменная может не содержать дополнительные данные, поэтому бессмысленно объединять другую строку.

Вы можете сделать:

char string[100] = "first string";

и затем вы можете объединить другую строку.

Второй вопрос

Первое, что нужно знать, это то, что строки в C - это символымассивы, содержащие NUL-завершение.

Когда вы делаете:

char b[3];

, вы получаете неинициализированный массив, то есть b может содержать что угодно - например, b = { ? , ? , ? }

Затем вы делаете:

strncpy(b, a, (int)(sizeof(b) - 1));

, что означает, что вы копируете 2 первых символа из a в b.

Итак, теперь мы знаем, что b равно b = { 'k' , 'e' , ? } Обратите внимание, что третий символ b все еще неинициализирован.

Так что когда вы делаете:

printf("string length: %i\n", (int)strlen(b));
printf("%s\n", b);

вы используете b, как будто это строка, но это не так.Нет завершения NUL.Следовательно, функции (printf, strlen) дают неверные результаты.Вызов этой функции с массивом символов без завершения NUL - неопределенное поведение, т. Е. Может произойти все, что угодно.

Кажется, что происходят две вещи:

a) Неинициализированныйсимвол в b просто является буквой 'E' (в одном из ваших примеров)

b) Строковый литерал "kenny" просто находится в памяти сразу после переменной b.

Таким образом, двухстрочная функция действительно видит строку "keEkenny", которая имеет len 8.

Чтобы исправить это, вы можете сделать:

strncpy(b, a, (int)(sizeof(b) - 1));
b[sizeof(b) - 1] = '\0';

или просто сделать:

char b[3] = { 0 };

, поскольку это инициализирует все b, то есть b = { '\0' , '\0' , '\0' }

0 голосов
/ 06 февраля 2019

Смысл строк C в том, что они довольно низкоуровневые, и есть ряд дополнительных вещей, о которых нужно помнить, а иногда и делать «вручную».

(C ++ std:: строки, напротив, являются почти полностью нормальными, высокоуровневыми типами.)

В ответ на ваши конкретные вопросы:

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

char str[10];
str[0] = 'c';
str[1] = 'a';
str[2] = 't';
str[3] = '\0';
printf("%s\n", str);

Но если вы пропустите явное присвоение str[3], оно будет работать беспорядочно.(Но если вы не создаете строки вручную, как это, вам не нужно сильно волноваться.)

Вы должны быть чрезвычайно осторожны при копировании строк с помощью strcpy.Вы должны убедиться, что строка назначения («буфер») достаточно велика.Ничто в Си никогда не позаботится об этом за вас - ничто не гарантирует, что место назначения достаточно велико;ничто не предупреждает вас, если оно недостаточно великоНо если он недостаточно велик, могут произойти самые странные вещи, в том числе и то, что он работает, хотя и не должен.(Формальное название для этого - «неопределенное поведение».)

В частности, если вы напишите

char string[] = "first string";
strcat(string, another_string);

, то, что вы получите, является ошибкой, чистой и простой. не верно, что «таким образом, вы не беспокоитесь о проблеме размера».Когда вы говорите char string[] = "...", компилятор определяет размер строки, достаточно большой, чтобы содержать инициализатор (и его \0), в данном случае 13 байтов для "first string".[] означает , а не означает «сделать эту строку достаточно большой для любого текста, который я когда-либо попытаюсь вставить в нее».

Вы должны быть еще более осторожны при использовании strncpy,На самом деле, я рекомендую вообще не использовать strncpy.То, что он на самом деле делает, необычно, особенно, сложно объяснить, и, как правило, совсем не то, что вы хотите.(Во-первых, если у вас есть копия меньше полной строки, это не добавляет `\ 0 'к месту назначения, что помогает объяснить, почему вы получили такие вещи, как" kekenny ".)

0 голосов
/ 06 февраля 2019

Если вы читаете документацию для strncpy, в ней совершенно ясно говорится, что он не будет добавлять терминатор NUL, если указанный вами размер не включает его:

Функция strncpy ()аналогично, за исключением того, что копируется максимум n байтов src.Предупреждение: если среди первых n байтов src нет нулевого байта, строка, помещенная в dest, не будет заканчиваться нулем.

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

strncpy(b, a, (int)(sizeof(b) - 1));
0 голосов
/ 06 февраля 2019

По определению в выражении:

char string[] = "first string"

string заполняется точно всем содержимым, которое он может содержать:

В памяти это выглядит так:

|f|i|r|s|t| |s|t|r|i|n|g|\0|?|?|?|
/// end of legal memory    ^

... иллюстрирует, почему следующее утверждение:

strcat(string, anythingElse);

равно неопределенное поведение .(Иначе известный как носовые демоны .)

Также относительно использования strncpy (,,) .Поскольку после его использования не гарантируется, что он будет содержать символ nul, рекомендуется всегда явно добавлять nul в нужное место в новой строке:

strncpy (target, source, n);
target[n] = 0;

Где в случаеваш пример, n == (sizeof(b) - 1)

Обратите внимание, что приведение к (int) не требуется в приведенном выше выражении при использовании sizeof в качестве типа 3-го параметра для strncpy(,,*), равного size_t:

char *strncpy (char Target_String[], const char Source_String[], size_t Max_Chars);

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

0 голосов
/ 06 февраля 2019

Вы должны добавить терминатор строки \ 0 к b, так или иначе.Printf ("% s \ n", b) остановится, когда найдет \ 0.

Это зависит от того, что у вас в памяти, иногда следует ожидать сбоя сегмента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...