В C ++ sizeof('a') == sizeof(char) == 1
. Это имеет интуитивный смысл, поскольку 'a'
является символьным литералом и sizeof(char) == 1
определено стандартом.
В C однако sizeof('a') == sizeof(int)
. То есть, похоже, что символьные литералы C на самом деле являются целыми числами. Кто-нибудь знает почему? Я могу найти множество упоминаний об этой причуде C, но без объяснения причин ее существования.
Ответы:
обсуждение на ту же тему
источник
char
переменная не является int, поэтому создание символьной константы равной единице - особый случай. И это легко использовать значение символа без его популяризации:c1 = c2;
. OTOHc1 = 'x'
- это понижающее преобразование. Самое главное,sizeof(char) != sizeof('x')
это серьезная языковая лажа. Что касается многобайтовых символьных констант: они причина, но они устарели.Первоначальный вопрос: «почему?»
Причина в том, что определение буквального символа эволюционировало и изменилось, пытаясь при этом оставаться обратно совместимым с существующим кодом.
В темные дни раннего C вообще не было типов. К тому времени, когда я впервые научился программировать на C, были введены типы, но у функций не было прототипов, чтобы сообщить вызывающей стороне, какие типы аргументов были. Вместо этого было стандартизовано, что все, что передается в качестве параметра, будет либо иметь размер int (включая все указатели), либо быть двойным.
Это означало, что когда вы писали функцию, все параметры, которые не были двойными, сохранялись в стеке как целые числа, независимо от того, как вы их объявляли, а компилятор помещал в функцию код, чтобы обработать это за вас.
Это делало вещи несколько непоследовательными, поэтому, когда K&R писали свою знаменитую книгу, они ввели правило, согласно которому символьный литерал всегда будет преобразован в int в любом выражении, а не только в параметре функции.
Когда комитет ANSI впервые стандартизировал C, они изменили это правило так, чтобы символьный литерал был просто int, поскольку это казалось более простым способом достижения того же результата.
Когда разрабатывался C ++, все функции должны были иметь полные прототипы (это еще не требуется в C, хотя это общепринято как хорошая практика). Из-за этого было решено, что символьный литерал может храниться в char. Преимущество этого в C ++ заключается в том, что функция с параметром char и функция с параметром int имеют разные сигнатуры. Это преимущество отсутствует в C.
Вот почему они разные. Эволюция ...
источник
void f(unsigned char)
Vsvoid f(signed char)
.f('a')
, вы, вероятно, захотите выбрать разрешение перегрузкиf(char)
для этого вызова, а неf(int)
. Относительные размерыint
иchar
, как вы говорите, не имеют значения.Я не знаю конкретных причин, по которым символьный литерал в C имеет тип int. Но в C ++ есть веская причина не идти этим путем. Учти это:
Вы ожидаете, что вызов print выберет вторую версию с символом. Наличие символьного литерала как int сделало бы это невозможным. Обратите внимание, что в C ++ литералы, содержащие более одного символа, по-прежнему имеют тип int, хотя их значение определяется реализацией. Итак,
'ab'
имеет типint
, а'a'
имеет типchar
.источник
используя gcc на моем MacBook, я пытаюсь:
который при запуске дает:
что предполагает, что символ состоит из 8 бит, как вы подозреваете, но символьный литерал - это int.
источник
Когда писали C, язык ассемблера MACRO-11 PDP-11 имел:
Подобные вещи довольно распространены на языке ассемблера - младшие 8 бит будут содержать код символа, остальные биты сброшены до 0. PDP-11 даже имел:
Это обеспечило удобный способ загрузки двух символов в младший и старший байты 16-битного регистра. Затем вы можете написать их в другом месте, обновив некоторые текстовые данные или экранную память.
Так что идея о том, что персонажи продвигаются до размера регистров, вполне нормальна и желательна. Но предположим, что вам нужно ввести A в регистр не как часть жестко запрограммированного кода операции, а откуда-то в основной памяти, содержащей:
Если вы хотите прочитать только букву «А» из этой основной памяти в регистр, какой из них вы бы прочитали?
Некоторые процессоры могут напрямую поддерживать только чтение 16-битного значения в 16-битный регистр, что будет означать, что чтение в 20 или 22 потребует удаления битов из 'X', и в зависимости от порядка байтов ЦП тот или иной потребуется перейти в младший байт.
Некоторым процессорам может потребоваться чтение с выравниванием по памяти, что означает, что наименьший задействованный адрес должен быть кратным размеру данных: вы можете читать с адресов 24 и 25, но не с 27 и 28.
Таким образом, компилятор, генерирующий код для ввода 'A' в регистр, может предпочесть потратить немного дополнительной памяти и закодировать значение как 0 'A' или 'A' 0 - в зависимости от порядка байтов, а также для обеспечения его правильного выравнивания ( т.е. не по нечетному адресу памяти).
Я предполагаю, что Си просто перенесли этот уровень ориентированного на ЦП поведения, думая о символьных константах, занимающих размеры регистров памяти, поддерживая общую оценку Си как «ассемблера высокого уровня».
(См. 6.3.3 на стр. 6-25 http://www.dmv.net/dec/pdf/macro.pdf )
источник
Я помню, как читал K&R и видел фрагмент кода, который читал символ за раз, пока он не достиг EOF. Поскольку все символы являются допустимыми символами для файла / входного потока, это означает, что EOF не может быть любым значением char. Код помещал прочитанный символ в int, затем проверял EOF, а затем преобразовывал в char, если это не так.
Я понимаю, что это не совсем ответ на ваш вопрос, но для остальных символьных литералов было бы разумно иметь sizeof (int), если бы был литерал EOF.
источник
Я не видел объяснения этому (литералы C char являются типами int), но вот что Страуструп сказал по этому поводу (из Design and Evolution 11.2.1 - Fine-Grain Resolution):
Так что по большей части это не должно вызывать проблем.
источник
Историческая причина этого заключается в том, что C и его предшественник B были первоначально разработаны на различных моделях миникомпьютеров DEC PDP с разным размером слова, которые поддерживали 8-битный ASCII, но могли выполнять арифметические операции только с регистрами. (Но не PDP-11; это было позже.) Ранние версии C определяли
int
как собственный размер слова машины, и любое значение, меньшее, чемint
нужно было расширить доint
, чтобы передать в функцию или из , или используется в побитовых, логических или арифметических выражениях, потому что именно так работает базовое оборудование.Вот почему в правилах целочисленного продвижения по-прежнему говорится, что
int
повышается любой тип данных, меньший, чемint
. Реализации C также могут использовать математику с дополнением до единицы вместо дополнения до двух по аналогичным историческим причинам. Причина, по которой восьмеричные символы экранирования и восьмеричные константы являются первоклассными гражданами по сравнению с шестнадцатеричными, аналогичным образом состоит в том, что те ранние миникомпьютеры DEC имели размер слов, делящийся на трехбайтовые блоки, но не четырехбайтовые полубайты.источник
char
состоял ровно из 3 восьмеричных цифрЭто правильное поведение, называемое «интегральное продвижение». Это может случиться и в других случаях (в основном, с бинарными операторами, если я правильно помню).
РЕДАКТИРОВАТЬ: Чтобы быть уверенным, я проверил свою копию Expert C Programming: Deep Secrets и подтвердил, что литерал char не начинается с типа int . Первоначально он имеет тип char, но когда он используется в выражении , он повышается до типа int . Следующее цитируется из книги:
источник
Не знаю, но предполагаю, что так было проще реализовать, и это не имело особого значения. Только в C ++ тип мог определять, какая функция будет вызвана, и ее нужно было исправить.
источник
Я действительно этого не знал. До появления прототипов все, что было меньше int, преобразовывалось в int при использовании в качестве аргумента функции. Это может быть частью объяснения.
источник
char
чтобыint
бы сделать это совершенно ненужным для символьных констант быть Интсом. Важно то, что язык трактует символьные константы по-другому (присваивая им другой тип)char
, чем переменные, и что необходимо, так это объяснение этой разницы.Это не относится к спецификации языка, но на аппаратном уровне процессор обычно имеет только один размер регистра - скажем, 32 бита - и поэтому всякий раз, когда он действительно работает с char (путем добавления, вычитания или сравнения), существует неявное преобразование в int при загрузке в регистр. Компилятор позаботится о правильном маскировании и сдвиге числа после каждой операции, так что если вы добавите, скажем, 2 к (unsigned char) 254, он будет обернутся до 0 вместо 256, но внутри кремния это действительно int пока вы не сохраните его обратно в память.
Это своего рода академический момент, потому что язык в любом случае мог указать 8-битный буквальный тип, но в этом случае спецификация языка более точно отражает то, что на самом деле делает процессор.
(чудаки x86 могут заметить, что есть, например, собственный addh op, который добавляет регистры короткой ширины за один шаг, но внутри ядра RISC это переводится в два шага: добавление чисел, затем расширение знака, как пара add / extsh на PowerPC)
источник
char
переменные имеют разные типы. Автоматические продвижения, которые отражают оборудование, не актуальны - они фактически анти-релевантны, потому чтоchar
переменные автоматически продвигаются, так что это не причина, по которой символьные литералы не относятся к типуchar
. Настоящая причина - многобайтовые литералы, которые сейчас устарели.