В языке C первый символ строковой переменной равен 0, но при печати 0 исчезает?

Я новичок в языке 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';
}


Надеюсь узнать, почему произошел такой результат

char c3[] = ""; определяет c3 как массив char размером 1. Следовательно, toLowerCase(c1, c3) будет писать в c3 за пределами границ: поэтому неопределенное поведение.
G.M. 17.07.2024 16:27

_Я написал следующий код для преобразования прописных букв в строчные... _ если вы делаете это для собственного назидания, отлично! Но поймите, что функция tolower уже существует для этой цели, и ее, безусловно, предпочтительнее использовать, чем использовать собственную.

yano 17.07.2024 16:29

В C массивы не расширяются автоматически, когда вы пишете в них за пределами границ. Вместо этого происходит неопределенное поведение. Это отличается от некоторых языков программирования более высокого уровня, которые определяют, когда вы пишете в массив за пределами границ, а затем автоматически расширяют массив. В C вы должны программировать все это самостоятельно, и вы обязаны убедиться, что в массиве достаточно места для того, что вы хотите сделать.

Andreas Wenzel 17.07.2024 16:37

Благодарю за ваш ответ. Я понял! Я понимаю, что если я присваиваю значение массиву, обязательным условием является запрос достаточного объема памяти. Я действительно новичок. Разобраться в этом действительно непросто.

shown zhang 17.07.2024 16:45
Стоит ли изучать 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
133
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы объявляете переменную 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 17.07.2024 17:15

@polozer Это неопределенное поведение при записи за массив. Другие объяснения — это наблюдения. Я подправлю это, чтобы добавить это.

Allan Wind 17.07.2024 17:19

@AllanWind В сочетании с приведенным выше ответом это помогло мне понять более тщательно. Спасибо.

shown zhang 18.07.2024 07:27
Ответ принят как подходящий

Вот трюк, который поможет вам понять, почему это происходит. Я добавил следующую строку:

    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. То есть печать этих указателей иногда может помочь понять ошибку или неожиданное поведение, но на них нельзя рассчитывать. Как уже упоминалось, запись за конец массива не определена. А с неопределённым поведением возможно всё.

Примечание: я не был первым, кто дал окончательный ответ, поэтому, если Г.М. хочет сделать свой ответ официальным, он, вероятно, заслуживает победы. Но я думал, что мой трюк с печатью указателей дает более конкретный ответ на вопрос ОП, и им стоит поделиться.

Streve Ford 17.07.2024 17:26

Распечатав адреса этих переменных, я понял проблему. с1 = 0x16f1ff5b4, c2 = 0x16f1ff5ac, c3 = 0x16f1ff5ab. Очевидно, что адрес c3 находится непосредственно перед c2, поэтому во время записи данных происходит перезапись. Кстати, я использую систему macOS и компилирую с помощью gcc. Спасибо!

shown zhang 18.07.2024 16:41

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