Смутное поведение 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)
Теперь посмотрим на каждый случай.
-
ch
определяется как тип char
, поэтому довольно простой.
-
В C sizeof('a')
совпадает с sizeof (int)
, поскольку символьная константа имеет тип integer.
Цитируя C11
,
Целочисленная символьная константа имеет тип int
. [...]
В C++ литерал символа имеет тип char
.
-
sizeof
- оператор времени компиляции (за исключением случаев, когда операндом является VLA), поэтому используется тип выражения. Как и раньше, все целочисленные символьные константы имеют тип int
, поэтому int
+ int
+ int
производит int
. Таким образом, тип операнда берется как int
.
-
"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.