Зависит ли результат sizeof от объявления строки?

Я получаю размер массива символов str с помощью следующего тестового кода:

int main()
{
    unsigned char str[] = "abcde";
    for(int j = 0; j <= 6; j++) {
        printf("str[%u]=%c;  %i\n", j, *(str+j), *(str+j));
    }
    printf("sizeof(str)=%lu\nstrlen(str)=%lu\n", sizeof(str), strlen(str));
    return 0;
}

Результат, как и ожидалось, равен 6, как можно увидеть на экране ниже:

str[0]=a;  97
str[1]=b;  98
str[2]=c;  99
str[3]=d;  100
str[4]=e;  101
str[5]=;  0
str[6]=;  0
sizeof(str)=6    //here it is!
strlen(str)=5

Однако, если я явно включу размерность строки (5) в его объявление, например:

unsigned char str[5] = "abcde";

теперь результат sizeof равен 5, а не ожидаемому 6, как видно из вывода функции:

str[0]=a;  97
str[1]=b;  98
str[2]=c;  99
str[3]=d;  100
str[4]=e;  101
str[5]=;  0
str[6];  8
sizeof(str)=5    // why 5 and not 6???
strlen(str)=5

Мой вопрос: в чем причина такого разного результата? Обратите внимание, что символ завершения правильно размещается после последнего символа строки, как видно из приведенных выше примеров. Спасибо за внимание.

В первой программе ваш массив состоит из шести элементов. Вы печатаете семь. Выход за пределы границ приводит к неопределенному поведению.

Some programmer dude 26.03.2024 16:49

Размер этого буфера недостаточен, и strlen() его поведение неопределенно. Вам повезло, потому что следующий байт, скорее всего, равен 0, но в нетривиальной программе это может легко привести к сбою.

tadman 26.03.2024 16:49

При str[] = "abcde" размер рассчитывается компилятором: 5 символов + нулевой терминатор. С помощью str[5] = "abcde" вы явно устанавливаете размер. Нулевой терминатор не включен.

dimich 26.03.2024 16:50

А в C, если вы не оставляете места для нулевого терминатора, он не будет добавлен. В случае unsigned char str[5] = "abcde"; у вас нет правильной строки с нулевым завершением. Передача его strlen также приведет к неопределенному поведению.

Some programmer dude 26.03.2024 16:50

Также обратите внимание, что и оператор sizeof, и функция strlen возвращают значение типа size_t. Чтобы напечатать значение size_t с помощью printf, вы должны использовать спецификатор формата %zu. Несовпадение типа аргумента и спецификатора формата также приводит к неопределенному поведению.

Some programmer dude 26.03.2024 16:51

С другой стороны, для любого массива указателей str и индексов j выражение *(str + j) в точности равно str[j]. Пожалуйста, используйте последний синтаксис индексации массива, так как его обычно легче читать и понимать с первого взгляда.

Some programmer dude 26.03.2024 16:53
j <= 6 приводит к неопределенному поведению программы в обоих случаях, поскольку вы разыменовываете str[6], что выходит за пределы
Ted Lyngmo 26.03.2024 17:47
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
7
84
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

strlen(str) во втором случае — неопределенное поведение.

Обратите внимание, что символ завершения правильно размещается после последнего символа строки, как видно из приведенных выше примеров.

Нет, это не так. Согласно C11 6.7.9 14:

Массив символьного типа может быть инициализирован литералом символьной строки или строкой UTF-8. литерал, необязательно заключенный в фигурные скобки. Последовательные байты строкового литерала (включая завершающий нулевой символ, если есть место или если массив имеет неизвестный размер) инициализируйте элементы массива.

Для завершающего нулевого символа нет места, поэтому он не добавляется. Таким образом, результат sizeof другой.

Хорошо, спасибо за ваш комментарий. Значит, символ \0, который я вижу в шестом элементе массива str[5], оказался там случайно и не был помещен туда компилятором, верно? (Я имею в виду случай с объявлением unsigned char str[5] = "abcde";

Guille 26.03.2024 18:16

@Guille Да, в массиве нет места для хранения завершающего нулевого символа. Первый символ строкового литерала инициализирует первый элемент массива. Второй символ строкового литерала инициализирует второй элемент массива и так далее. Но для шестого элемента строкового литерала нет соответствующего свободного элемента массива.

Vlad from Moscow 26.03.2024 19:31
Ответ принят как подходящий

Строковый литерал "abcde", используемый в качестве инициализатора массива str, имеет 6 символов, включая завершающий нулевой символ '\0'.

Но вы явно объявили массив только с символами 5:

unsigned char str[5] = "abcde";
              ^^^^^^  

Поэтому даже неважно, как вы инициализируете массив, потому что вы явно указали его размер, равный 5, а sizeof( unsigned char ) всегда равен 1. Итак, sizeof( str ), очевидно, равно 5.

Обратите внимание, что в этом случае ваш массив не содержит строки, поскольку он не может вместить завершающий нулевой символ '\0' строкового литерала. Например, вызов функции strlen для массива вызывает неопределенное поведение.

В отличие от C в C++ такое заявление недействительно. В C++ нужно написать как минимум

unsigned char str[6] = "abcde";

или как вы написали первое объявление массива, например

unsigned char str[] = "abcde";

В последнем случае количество элементов массива равно количеству символов в строковом литерале.

Также для вывода значений типа size_t следует использовать спецификатор преобразования zu вместо lu, потому что, как правило, не обязательно, чтобы тип size_t был псевдонимом типа unsigned long. В некоторых системах это может быть псевдоним типа unsigned long long.

Из стандарта C (7.19 Общие определения <stddef.h>)

Рекомендуемая практика

4 Типы, используемые для size_t и ptrdiff_t, не должны иметь целочисленные значения. ранг конверсии выше, чем у подписанного длинного целого, если только реализация поддерживает объекты, достаточно большие, чтобы это было необходимо.

Итак, вам нужно написать

printf("sizeof(str)=%zu\nstrlen(str)=%zu\n", sizeof(str), strlen(str));

спасибо за ваш ответ, я принял. Тем не менее, учитывая случай с объявлением/определением unsigned char str[5] = "abcde", очень удивительно (для меня), что программа выводит ноль (\0), похоже, помещенный в 6-й элемент массива, т.е. str[ 5]='\0', как будто компилятор понимает, что оно должно быть там...

Guille 26.03.2024 18:34

@Guille str[5] находится за пределами массива, и результат доступа — UB. Вам просто повезло!

Ian Abbott 26.03.2024 18:47

@Vlad Более того: если инициализировать строку как unsigned char str[5] = «abcde», компилятор не выдает мне предупреждения, и ноль ta РАЗМЕЩАЕТСЯ в 6-м элементе str[5]. Если я изменю 5 на 4, определив unsigned char str[4] = «abcde», то компилятор выдаст мне предупреждение. Однако и в этом случае терминатор \0 РАЗМЕЩАЕТСЯ в 4-м элементе массива str[4]. Очень странно...

Guille 26.03.2024 18:51

@Ian Извините, это не может быть просто удача. Я проверял это десятки раз, пятый элемент ВСЕГДА равен нулю, а следующие имеют, как и следовало ожидать, случайные значения. Но не пятый, как я уже сказал, он всегда ноль.

Guille 26.03.2024 18:56

@Guille Это неопределенное поведение. Просто получилось так, что после последнего элемента массива память инициализируется нулем. Но в целом это просто случайность. В объявленном массиве всего 5 элементов. Оно не может быть автоматически увеличено.

Vlad from Moscow 26.03.2024 19:38

@Guille Просто поместите до и после массива две другие переменные, например, char s1[2] = "12"; char str[5] = "abcde"; char s2[2] = "34"; и проверьте свою программу. Например, выведите значение strlen(str);

Vlad from Moscow 26.03.2024 19:51

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