Смутное поведение sizeof с символами

#include <stdio.h>
#include <string.h>

int main(void)
{
    char ch='a';

    printf("sizeof(ch)          = %d\n", sizeof(ch));
    printf("sizeof('a')         = %d\n", sizeof('a'));
    printf("sizeof('a'+'b'+'C') = %d\n", sizeof('a'+'b'+'C'));
    printf("sizeof(\"a\")       = %d\n", sizeof("a"));
}

Эта программа использует sizeof для расчета размеров. Почему размер 'a' отличается от размера ch (где ch='a')?

sizeof(ch)          = 1
sizeof('a')         = 4
sizeof('a'+'b'+'C') = 4
sizeof("a")         = 2

Ответы

Ответ 1

TL; DR - sizeof работает с типом операнда.

  • sizeof(ch) == sizeof (char) ------------------- (1)
  • sizeof('a') == sizeof(int) -------------------- (2)
  • sizeof ('a'+ 'b' + 'c') == sizeof(int) --- (3)
  • sizeof ("a") == sizeof (char [2]) ---------- (4)

Теперь посмотрим на каждый случай.

  1. ch определяется как тип char, поэтому довольно простой.

  2. В C sizeof('a') совпадает с sizeof (int), поскольку символьная константа имеет тип integer.

    Цитируя C11,

    Целочисленная символьная константа имеет тип int. [...]

    В C++ литерал символа имеет тип char.

  3. sizeof - оператор времени компиляции (за исключением случаев, когда операндом является VLA), поэтому используется тип выражения. Как и раньше, все целочисленные символьные константы имеют тип int, поэтому int + int + int производит int. Таким образом, тип операнда берется как int.

  4. "a" - это массив из двух char s, 'a' и 0 (null-terminator) (нет, он не распадается на указатель на первый элемент типа массива), поэтому размер такой же, как и для массива с двумя элементами char.


Тем не менее, sizeof создает результат типа size_t, поэтому для печати результата необходимо использовать спецификатор формата %zu.

Ответ 2

В C 'a' является константой типа int. Это не char. Поэтому sizeof('a') будет таким же, как sizeof(int).

sizeof(ch) совпадает с sizeof(char). (Стандарт C гарантирует, что все буквенно-цифровые константы - и некоторые другие - формы 'a' могут вписываться в char, поэтому char ch='a'; всегда хорошо определен.)

Обратите внимание, что в C++ 'a' является литералом типа char; еще одна разница между C и C++.

В C sizeof("a") - sizeof(char[2]) который равен 2. sizeof не вызывает распад типа массива указателю.

В C++ sizeof("a") - sizeof(const char[2]) который равен 2. sizeof не вызывает распад типа массива указателю.

В обоих языках 'a'+'b'+'C' является типом int, в связи с которым C++ подразумевается продвижение интегральных типов.

Ответ 3

Прежде всего, результатом sizeof является тип size_t, который должен быть напечатан с помощью спецификатора формата %zu. Игнорирование этой части и предположение int составляет 4 байта, тогда

  • printf("sizeof(ch) %d\n",sizeof(ch)); будет печатать 1 в C и 1 в C++.

    Это связано с тем, что для каждого char гарантируется 1 байт на обоих языках.

  • printf("sizeof('a') %d\n",sizeof('a')); будет печатать 4 в C и 1 в C++.

    Это связано с тем, что символьные литералы имеют тип int в C по историческим причинам 1) но они имеют тип char в C++, потому что этот здравый смысл (и ISO 14882) диктует.

  • printf("sizeof('a'+'b'+'C) %d\n",sizeof('a'+'b'+'C')); будет печатать 4 на обоих языках.

    В C результирующий тип int + int + int является естественным int. В C++ мы имеем char + char + char. Но + вызывает неявные правила продвижения по типу, поэтому в конечном итоге мы заканчиваем int в любом случае.

  • printf("sizeof(\"a\") %d\n",sizeof("a")); будет печатать 2 на обоих языках.

    Строковый литерал "a" имеет тип char[] в C и const char[] в C++. В любом случае у нас есть массив, состоящий из a и нулевого терминатора: два символа.

    В качестве побочного примечания это происходит потому, что массив "a" не распадается на указатель на первый элемент, когда операнд sizeof. Должны ли мы спровоцировать распад массива, например, sizeof("a"+0), тогда вместо этого мы получим размер указателя (вероятно, 4 или 8).


1) Где-то в темные века не было никаких типов, и все, что вы написали, сводилось бы к int независимо от того. Затем, когда Деннис Ричи начал готовить вместе какой-то стандарт де-факто для C, он, по-видимому, решил, что символьные литералы всегда должны быть продвинуты до int. А потом, когда C стандартизовали, они сказали, что символьные литералы просто int.

При создании C++, Бьярне Страуструп признать, что все это не имеет особого смысла и сделал характер типа литералов char, как они должны быть. Но комитет С упрямо отказывается исправить этот языковой недостаток.

Ответ 4

Как отмечали другие, стандарт языка C определяет тип символьной константы как int. Историческая причина этого заключается в том, что C и его предшественник B были первоначально разработаны на миникомпьютерах DEC PDP с различными размерами слов, которые поддерживали 8-разрядную ASCII, но могли выполнять арифметику только для регистров. Ранние версии C, определяемые int являются родным размером слова машины, а любое значение, меньшее чем int необходимо расширять до int, чтобы быть переданным или из функции, или использоваться в поразрядном, логическом или арифметическом выражении, потому что именно так работало основное аппаратное обеспечение.

Именно поэтому целые правила продвижения по-прежнему говорят, что любой тип данных, меньший, чем int, продвигается до int. В реализациях C также разрешено использовать одну-дополняющую математику вместо двухкомпонента по аналогичным историческим причинам, а тот факт, что символ избегает по умолчанию восьмеричных и восьмеричных констант, начинается с 0 или нужных потребностей \x или 0x том, что эти ранние DEC-миникомпьютеры имел размер слов, разделяемый на трехбайтовые куски, но не на четыре байта.

Автоматическое продвижение к int вызывает ничего, кроме проблем сегодня. (Сколько программистов знают, что умножение двух выражений uint32_t вместе является неопределенным поведением, поскольку некоторые реализации определяют int как 64 бита в ширину, язык требует, чтобы любой тип более низкого ранга, чем int должен был продвигаться к подписанному int, результат умножения двух int multipicands имеет тип int, умножение может переполнить подписанный 64-разрядный продукт, и это неопределенное поведение?) Но вот причина C и C++ застряли в нем.

Ответ 5

Я предполагаю, что код был скомпилирован в C.
В C, 'a' трактуется как int типа и int имеет размер 4. В C++, 'a' трактуется как char типа, и если вы пытаетесь компиляции кода в cpp.sh, он должен вернуть 1.