Я новичок в языке C. Я написал следующий код для преобразования прописных букв в строчные и копирования их в новую переменную. Переменная c2 была только объявлена и инициализирована, но при печати пропал первый символ 0 переменной c2, что меня очень смутило. Код следующий:
#include <stdio.h>
void toLowerCase(const char *s, char *c);
int main()
{
char c1[] = "0Xe8Fa";
char c2[] = "0Xe8Fa";
char c3[] = "";
toLowerCase(c1, c3);
printf("c1 = %s\n", c1); // c1 = 0Xe8Fa
printf("c2 = %s\n", c2); // c2 = xe8fa why zero fly?
printf("c3 = %s\n", c3); // c3 = 0xe8fa
return 0;
}
void toLowerCase(const char *s, char *c)
{
int i;
i = 0;
while (s[i] != '\0')
{
if (s[i] >= 'A' && s[i] <= 'Z')
{
c[i] = s[i] + ('a' - 'A');
}
else
{
c[i] = s[i];
}
++i;
}
c[i] = '\0';
}
Надеюсь узнать, почему произошел такой результат
_Я написал следующий код для преобразования прописных букв в строчные... _ если вы делаете это для собственного назидания, отлично! Но поймите, что функция tolower уже существует для этой цели, и ее, безусловно, предпочтительнее использовать, чем использовать собственную.
В C массивы не расширяются автоматически, когда вы пишете в них за пределами границ. Вместо этого происходит неопределенное поведение. Это отличается от некоторых языков программирования более высокого уровня, которые определяют, когда вы пишете в массив за пределами границ, а затем автоматически расширяют массив. В C вы должны программировать все это самостоятельно, и вы обязаны убедиться, что в массиве достаточно места для того, что вы хотите сделать.
Благодарю за ваш ответ. Я понял! Я понимаю, что если я присваиваю значение массиву, обязательным условием является запрос достаточного объема памяти. Я действительно новичок. Разобраться в этом действительно непросто.
Вы объявляете переменную c3
размером 1
со значением ""
, таким же, как { '\0' }
. Размер массивов не изменяется автоматически, и запись за конец массива не определена.
Я заметил, что компилятор размещает переменные так &c2 == &c3[1]
. Это означает, что toLowerCase()
записывает начальную c1[0] == '0'
в c3[0]
и остальную часть строки c1[1]
, начиная с c3[1]
или c2[0]
:
printf("c2 (%p) = %s\n", (void *) &c2, c2); // c2 = xe8fa why zero fly?
printf("c3 (%p) = %s\n", (void *) &c3, c3); // c3 = 0xe8fa
напечатает:
c2 (0x7ffef4e11ed2) = xe8fa
c3 (0x7ffef4e11ed1) = 0xe8fa
Это также неопределенное поведение для printf("c3 = %s\n", c3)
, поскольку c3
— это массив размером 1, а не строка, завершающаяся '\0'.
Решение состоит в том, чтобы передать массив, достаточно большой, чтобы хранить результат toLowerCase()
, например, объявив char c3[sizeof c1];
.
Другой вариант — динамически выделить массив результатов или вернуть его:
char *toLowerCase(const char *s)
{
char *c = malloc(strlen(s) + 1);
if (!c) return NULL;
// ...
return c;
}
// ...
char *c3 = toLowerCase(c1);
или измените второй аргумент, чтобы он был указателем на указатель:
void toLowerCase(const char *s, char **c)
{
*c = malloc(strlen(s) + 1);
if (!*c) return NULL;
// ...
}
// ...
char *c3;
toLowerCase(c3, &c3);
Это на самом деле не отвечает на вопрос, почему исчезает первый символ c2.
@polozer Это неопределенное поведение при записи за массив. Другие объяснения — это наблюдения. Я подправлю это, чтобы добавить это.
@AllanWind В сочетании с приведенным выше ответом это помогло мне понять более тщательно. Спасибо.
Вот трюк, который поможет вам понять, почему это происходит. Я добавил следующую строку:
printf("addr of c1=%p, addr of c2=%p, addr of c3=%p\n", c1, c2, c3);
«%p» печатает фактический адрес переменной. Вот мой результат:
$ gcc x.c
$ ./a.out
addr of c1=0x7ffef0c1389a, addr of c2=0x7ffef0c138a1, addr of c3=0x7ffef0c13899
c1 = xe8fa
c2 = 0Xe8Fa
c3 = 0xe8fa
Обратите внимание, что мой результат отличается от вашего. У вас поврежден c2, а у меня поврежден c1. Давайте сначала посмотрим на мои результаты.
Если вы посмотрите на эти адреса, вы увидите, что c3 находится по самому низкому адресу из трех, заканчивающемуся на 899. Затем c1 с адресом, заканчивающимся на 89a, что на единицу выше. Затем c2 с адресом, заканчивающимся на 8a1, что на 7 больше. Интересно, что компилятор C не расположил переменные в памяти в том порядке, в котором они были объявлены. Но компилятор НЕ ДОЛЖЕН располагать их в объявленном порядке, если они не являются членами структуры. Так что GCC здесь не сделал ничего плохого.
Вот как я бы нарисовал расположение переменных в памяти (показывая только последние 3 цифры адреса):
c2: 8a1 (? bytes)
c1: 89a (7 bytes)
c3: 899 (1 byte)
Учитывая эти адреса и исходный код, мы можем сделать вывод, что компилятор выделил 1 байт для c3 и 7 байт для c1. Вывод программы не дает нам прямых доказательств размера памяти c2, но мы можем предположить, что он также равен 7 байтам.
Во время выполнения программа копирует данные из c1 в c3. Первый байт, «0», попадает в адрес 899, то есть c3[0]. Второй байт, «X», переходит в адрес 89a, то есть c1[0]. И так далее. Вы пишете после конца c3, потому что ему выделен только 1 байт, а c1 оказался в памяти сразу после c3, поэтому он был затерт.
Итак, почему ваш результат отличается от моего? Судя по всему, происходит то же самое, но ваш компилятор расположил переменные в другом порядке. Я предполагаю, что вы используете компилятор, отличный от моего (может быть, Microsoft?).
Наконец, помните об ограничениях этого трюка. Компиляторы часто вставляют заполнение (неиспользуемое пространство) между переменными. Или предположим, что c3 оказался по старшему адресу. Написание дальше конца не затронуло бы c1 или c2. То есть печать этих указателей иногда может помочь понять ошибку или неожиданное поведение, но на них нельзя рассчитывать. Как уже упоминалось, запись за конец массива не определена. А с неопределённым поведением возможно всё.
Примечание: я не был первым, кто дал окончательный ответ, поэтому, если Г.М. хочет сделать свой ответ официальным, он, вероятно, заслуживает победы. Но я думал, что мой трюк с печатью указателей дает более конкретный ответ на вопрос ОП, и им стоит поделиться.
Распечатав адреса этих переменных, я понял проблему. с1 = 0x16f1ff5b4, c2 = 0x16f1ff5ac, c3 = 0x16f1ff5ab. Очевидно, что адрес c3 находится непосредственно перед c2, поэтому во время записи данных происходит перезапись. Кстати, я использую систему macOS и компилирую с помощью gcc. Спасибо!
char c3[] = "";
определяетc3
как массивchar
размером 1. Следовательно,toLowerCase(c1, c3)
будет писать вc3
за пределами границ: поэтому неопределенное поведение.