Чому буквені букви символів С замість знаків?


103

У C ++ sizeof('a') == sizeof(char) == 1. Це має інтуїтивний сенс, оскільки 'a'є символом буквальним, і sizeof(char) == 1як визначено стандартом.

У C однак sizeof('a') == sizeof(int). Тобто, виявляється, що буквалі символів C - це фактично цілі числа. Хтось знає, чому? Я можу знайти багато згадок про цю вигадку C, але немає пояснень, чому вона існує.


sizeof просто поверне розмір байта, чи не так? Хіба не знаки та ін є рівні за розміром?
Джош Смітон

1
Це, мабуть, залежить від компілятора (та архітектури). Хочете сказати, що ви використовуєте? Стандарт (принаймні до 1989 року) був дуже вільним.
dmckee --- кошеня колишнього модератора

2
немає. char завжди на 1 байт великий, тому sizeof ('a') == 1 завжди (у c ++), тоді як int теоретично може бути розміром 1, але для цього потрібен байт має принаймні 16 біт, що дуже малоймовірно: ) тому sizeof ('a')! = sizeof (int) дуже ймовірно в C ++ у більшості реалізацій
Йоханнес Шауб - litb

2
... хоча це завжди неправильно в C.
Йоханнес Шауб - ліб

22
'a' - це int в періоді C. C потрапив туди першим - C склав правила. C ++ змінив правила. Ви можете стверджувати, що правила C ++ мають більше сенсу, але зміна правил C принесла б більше шкоди, ніж користі, тому комітет стандартів C розумно цього не торкався.
Джонатан Леффлер

Відповіді:


36

дискусія з цієї ж теми

"Більш конкретно, цілісні рекламні акції. У K&R C практично неможливо було використати значення символу без того, щоб його спочатку сприяли до int, тому введення первинного символу int в першу чергу усунуло цей крок. Існували і все ще є багатозначні символи константи, такі як 'abcd' або скільки завгодно, помістяться в int. "


Багатосимвольні константи не є портативними, навіть між компіляторами на одній машині (хоча GCC, здається, є самовідповідним для всіх платформ). Дивіться: stackoverflow.com/questions/328215
Джонатан Леффлер

8
Зазначу, що а) ця цитата не надається; цитата просто говорить: "Чи не погоджуєтесь ви з цією думкою, яка була розміщена в минулій темі, що обговорювала питання, про яке йдеться?" ... і б) Це смішно , тому що charзмінна не є цілою, тому перетворення константи символів у один є особливим випадком. І це легко використовувати значення символу без його популяризації: c1 = c2;. OTOH, c1 = 'x'це конверсія вниз. Найголовніше sizeof(char) != sizeof('x'), що серйозний мовний ботш. Що стосується багатобайтових констант символів: вони є причиною, але вони застаріли.
Джим Балтер

27

Оригінальне питання "чому?"

Причина полягає в тому, що визначення буквального символу еволюціонувало та змінювалося, намагаючись залишатися назад сумісним з існуючим кодом.

У темні дні раннього С типів взагалі не було. На той момент, коли я вперше навчився програмувати на C, типи були введені, але функції не мали прототипів, щоб сказати абоненту, що таке типи аргументів. Натомість було стандартизовано, що все, що передається як параметр, буде або розміром з int (сюди включаються всі вказівники), або буде подвійним.

Це означало, що під час написання функції всі параметри, які не були подвійними, зберігалися в стеці як ints, незалежно від того, як ви їх оголосили, і компілятор поставив код у функцію, щоб це обробити для вас.

Це зробило речі дещо непослідовними, тому коли K&R писала свою відому книгу, вони встановлювали правило, що літеральний символ завжди буде переведений на int у будь-якому виразі, а не лише у функціональному параметрі.

Коли комітет ANSI вперше стандартизував C, вони змінили це правило, щоб буквальний символ був би просто int, оскільки це здавалося більш простим способом досягнення того ж самого.

Коли C ++ розроблявся, всі функції повинні були мати повноцінні прототипи (це все ще не потрібно в C, хоча воно є загальноприйнятим як хороша практика). Через це було вирішено, що літеральний символ може зберігатися в картці. Перевага цього в C ++ полягає в тому, що функції з параметром char та функції з параметром int мають різні підписи. Ця перевага не стосується C.

Ось чому вони різні. Еволюція ...


2
+1 від мене за те, що я відповів "чому?". Але я не згоден з останнім твердженням - "Перевага цього в C ++ полягає в тому, що функція з параметром char і функція з параметром int мають різні підписи" - у C ++ все ще можливо, що для 2 функцій є параметри однаковий розмір і різні підписи, наприклад , void f(unsigned char)Vs void f(signed char).
Пітер К

3
@PeterK Джон міг би сказати це краще, але те, що він говорить, є по суті точним. Якщо ви пишете f('a'), мотивацією до зміни C ++ було, ймовірно, ви хочете вибрати f(char)для цього дзвінка роздільну здатність перевантаження f(int). Відносні розміри intта char, як ви кажете, не стосуються.
zwol

21

Я не знаю конкретних причин, чому буквений символ у C має тип int. Але в C ++ є вагомі причини не йти цим шляхом. Врахуйте це:

void print(int);
void print(char);

print('a');

Ви можете очікувати, що заклик до друку обирає другу версію із знаком. Наявність символу, що є буквальним буквою, є неможливим. Зауважте, що в C ++ літералах, що мають більше одного символу, все ще є тип int, хоча їх значення визначено реалізацією. Отже, 'ab'має тип int, а 'a'має тип char.


Так, "Дизайн та еволюція C ++" говорить, що перевантажені процедури введення / виведення були основною причиною, коли C ++ змінив правила.
Макс Лібберт

5
Макс, так я обдурив. Я заглянув у стандарт у розділі сумісності :)
Йоханнес Шауб -

18

використовуючи gcc на своєму MacBook, я намагаюся:

#include <stdio.h>
#define test(A) do{printf(#A":\t%i\n",sizeof(A));}while(0)
int main(void){
  test('a');
  test("a");
  test("");
  test(char);
  test(short);
  test(int);
  test(long);
  test((char)0x0);
  test((short)0x0);
  test((int)0x0);
  test((long)0x0);
  return 0;
};

який при запуску дає:

'a':    4
"a":    2
"":     1
char:   1
short:  2
int:    4
long:   4
(char)0x0:      1
(short)0x0:     2
(int)0x0:       4
(long)0x0:      4

що говорить про те, що символ - це 8 біт, як ви підозрюєте, але літеральний символ - це int.


7
+1 за цікавість. Люди часто думають, що sizeof ("a") і sizeof ("") є char * 'і повинні давати 4 (або 8). Але насправді вони є char [] у цій точці (sizeof (char [11]) дає 11). Пастка для новачків.
paxdiablo

3
Буквал символів не просувається до int, це вже int. Промоція не відбувається, якщо об'єкт є операндом оператора sizeof. Якби це було, це перемогло б розмір мети.
Кріс Янг

@Chris Young: Я. Перевірити. Дякую.
dmckee --- кошеня колишнього модератора

8

Ще коли писали C, мова складання PDA-11 MACRO-11 мала:

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

Такі речі є досить поширеними в мові складання - низькі 8 біт містять код символів, інші біти очищені до 0. PDP-11 навіть мав:

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

Це забезпечило зручний спосіб завантаження двох символів у низький і високий байти 16-бітного регістру. Потім ви можете записати їх в іншому місці, оновивши деякі текстові дані або екранні пам'яті.

Отже, ідея просування персонажів до реєстрації розміру цілком нормальна і бажана. Але, скажімо, вам потрібно ввести "А" в реєстр не як частина жорстко закодованого коду, а десь із основної пам'яті, що містить:

address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'

Якщо ви хочете прочитати лише "A" з цієї основної пам'яті в реєстр, який би ви читали?

  • Деякі процесори можуть безпосередньо підтримувати зчитування 16-бітового значення в 16-бітовому регістрі, що означатиме, що читання в 20 або 22 вимагає очищення бітів з "X" і залежно від витривалості центрального процесора того чи іншого. знадобиться перехід на байт низького порядку.

  • Деякі ЦП можуть вимагати зчитування з вирівнюванням пам’яті, а це означає, що найнижча адреса повинна бути кратною розміру даних: ви можете читати з адрес 24 і 25, але не 27 і 28.

Таким чином, компілятор, що генерує код для отримання "A" в реєстр, може вважати за краще витрачати трохи додаткової пам'яті та кодувати значення як 0 'A' або 'A' 0 - залежно від ендіантності, а також забезпечуючи його правильне вирівнювання ( тобто не за непарною адресою пам'яті).

Я здогадуюсь, що C просто переніс цей рівень поведінки, орієнтованої на процесор, і думав про константи символів, що займають регістрові розміри пам'яті, висловлюючи загальну оцінку C як "асемблера високого рівня".

(Див. 6.3.3 на сторінці 6-25 http://www.dmv.net/dec/pdf/macro.pdf )


5

Я пам'ятаю, як читав K&R і бачив фрагмент коду, який би читав символ за той час, поки він не потрапив на EOF. Оскільки всі символи є дійсними символами, які мають бути у файлі / вхідному потоці, це означає, що EOF не може бути жодним знаком char. Код зробив, щоб поставити прочитаний символ в int, потім протестувати EOF, а потім перетворити на char, якщо цього не було.

Я усвідомлюю, що це точно не відповідає вашому запитанню, але було б певно сенс для решти символьних букв бути розміром (int), якби літерал EOF був.

int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;

while ((r = getc(file)) != EOF)
{
  *(p++) = (char) r;
}

Я не думаю, що 0 є дійсним символом.
gbjbaanb

3
@gbjbaanb: Звичайно, так і є. Це нульовий символ. Подумай над цим. Чи вважаєте ви, що файл не повинен містити нульових байтів?
P тато

1
Читайте wikipedia - "Фактичне значення EOF - це залежне від системи негативне число, як правило, -1, яке гарантовано нерівне до будь-якого дійсного коду символів."
Малкс

2
Як каже Малкс - EOF - це не типовий тип - це тип int. getchar () та друзі повертають міжнародний код, який може містити будь-які символи, а також EOF без конфлікту. Це дійсно не вимагатиме буквальних символів, щоб мати тип int.
Майкл Берр

2
EOF == -1 прийшов довго після символьних констант C, тому це не відповідь і навіть не актуально.
Джим Балтер

5

Я не бачив обґрунтування цього (цілі літератури C char є int типами), але ось що Stroustrup повинен був сказати про це (з Design and Evolution 11.2.1 - Fine-Grain Resolution):

У C - тип буквеного символу, такий як 'a'є int. Дивно, але 'a'тип charC ++ не викликає проблем із сумісністю. За винятком патологічного прикладу sizeof('a'), кожна конструкція, яка може бути виражена як C, так і C ++, дає однаковий результат.

Тож здебільшого це не повинно викликати проблем.


Цікаво! Нібито суперечить тому, що інші говорили про те, як комітет зі стандартів С "розумно" вирішив не видаляти цю
химерність

2

Історична причина цього полягає в тому, що C та його попередник B спочатку були розроблені на різних моделях міні-комп'ютерів PDE PDP з різними розмірами слів, які підтримували 8-бітний ASCII, але могли виконувати арифметику лише в регістрах. (Не PDP-11, однак, це з’явилося пізніше.) Ранні версії C визначали intяк нативний розмір слова машини, і будь-яке значення, менше, ніж intпотрібно для розширення int, щоб перейти до функції або від неї або використовується у побітовому, логічному чи арифметичному вираженні, оскільки саме так працювало базове обладнання.

Ось чому правила цілого просування все ще говорять про те, що будь-який тип даних, менший від an int, рекламується int. В реалізації C також дозволяється використовувати математику одного доповнення замість двох доповнення з аналогічних історичних причин. Причина того, що восьмеричні символи тікають, а восьмеричні константи є першокласними громадянами порівняно з шестигранними, - це те, що у тих ранніх міні-комп’ютерів DEC розміри слів ділилися на трибайтові шматки, але не чотирибайтові.


... і charбуло рівно 3 восьмеричних цифри
Антті

1

Це правильна поведінка, яка називається "цілісне просування". Це може траплятися і в інших випадках (переважно двійкові оператори, якщо я правильно пам’ятаю).

EDIT: Просто для впевненості, я перевірив свою копію програмування Expert C: Deep Secrets , і я підтвердив, що літера-літера не починається з типу int . Спочатку він має тип char, але коли він використовується в виразі , він переходить до int . З книги цитується наступне:

Літерали символів мають тип int, і вони потрапляють туди, дотримуючись правил просування з типу char. Це занадто коротко висвітлено в K&R 1, на сторінці 39, де написано:

Кожен знак в виразі перетворюється на int .... Зауважте, що всі float-фрази в виразі перетворюються в подвійні .... Оскільки аргумент функції - це вираз, перетворення типів відбувається також при передачі аргументів функції: в зокрема, char і короткий перетворюються на int, float стає подвійним.


Якщо вірити іншим коментарям, вираз 'a' починається з типу int - просування типу не проводиться всередині sizeof (). Це "a" має тип int - лише химерність C, здається.
j_random_hacker

2
Напівкоксу буквальним робить мають тип Int. Стандарт ANSI / ISO 99 називає їх «цілими константами символів» (щоб їх відрізняти від «констант широкого характеру», які мають тип wchar_t) і конкретно говорить: «Ціла константа символів має тип int».
Майкл Берр

Я мав на увазі те, що він не починається з типу int, а скоріше перетворюється на int з char (відповідь відредагована). Звичайно, це, мабуть, не стосується нікого, крім письменників-компіляторів, оскільки перетворення здійснюється завжди.
PolyThinker

3
Немає! Якщо ви прочитаєте стандарт ANSI / ISO 99 C, ви побачите, що в C вираз 'a' починається з типу int. Якщо у вас є функція недійсним п (INT) і змінні обвуглені с, то F (C) буде виконувати інтегральне просування, а п ( «а») не , як тип «а» вже інт. Дивно, але правда.
j_random_hacker

2
"Просто для переконання" - Ви можете бути впевненішими, прочитавши твердження: "Літеральні символи мають тип int". "Я можу лише припустити, що це було однією з тихих змін", - ви припускаєте неправильно. Літеральні символи в С завжди були типу int.
Джим Балтер

0

Я не знаю, але я думаю, що було легше реалізувати це таким чином, і це насправді не мало значення. Лише C ++, коли тип міг визначити, яка функція буде викликана, її потрібно виправити.


0

Я цього не знав насправді. Перш ніж існували прототипи, все вужче, ніж int, перетворювалося на int, використовуючи його як аргумент функції. Це може бути частиною пояснення.


1
Ще одна погана "відповідь". Автоматичне перетворення charдо intв зробить його зовсім непотрібним для символьних констант. Що важливо, це те, що мова розглядає константи символів по-різному (надаючи їм різний тип) від charзмінних, і що потрібно - це пояснення цієї різниці.
Джим Балтер

Дякуємо за пояснення, які ви дали нижче. Можливо, ви хочете більш детально описати своє пояснення у відповіді, де воно належить, його можна буде проголосувати і легко побачити відвідувачам. Також я ніколи не говорив, що маю тут хорошу відповідь. Тому ваше ціннісне судження не допомагає.
Blaisorblade

0

Це лише дотично до мовної специфікації, але в апаратному забезпеченні процесор зазвичай має лише один розмір реєстру - 32 біта, скажімо так, - і тому, коли він насправді працює на графіку (додаючи, віднімаючи або порівнюючи), є неявна конверсія в int при завантаженні в регістр. Компілятор піклується про правильне маскування та зміщення числа після кожної операції, так що якщо ви додасте, скажімо, 2 до (неподписаний знак) 254, він обернеться до 0 замість 256, але всередині кремнію це справді цілий поки ви не збережете його в пам'ять.

Це свого роду академічний момент, оскільки мова могла б у будь-якому разі вказати 8-бітний буквальний тип, але в цьому випадку мовна специфікація більш детально відображає те, що насправді робить ЦП.

(x86 winks може зауважити, що є, наприклад, нативна опція addh, яка додає короткі широкі регістри за один крок, але всередині ядра RISC це перекладається на два етапи: додайте числа, потім розгорніть знак, як пара додати / extsh на PowerPC)


1
Ще одна неправильна відповідь. Тут питання полягає в тому, чому буквені символи та charзмінні мають різні типи. Автоматичні рекламні акції, що відображають обладнання, не мають значення - вони насправді є антирелевантними, оскільки charзмінні автоматично рекламуються, тому це не є причиною того, що літератори символів не мають типу char. Справжня причина - багатобайтові літерали, які зараз застаріли.
Джим Балтер

@Jim Balter Багатобайтові літерали зовсім не застаріли; є багатобайтові символи Unicode та UTF.
Crashworks

@Crashworks Ми говоримо про багатобайтові літерали символів , а не багатобайтові рядкові літерали. Спробуйте звернути увагу.
Джим Балтер

4
Chrashworks писав символів . Ви повинні були написати, що літератури широкого характеру (скажімо, L'à ') беруть більше байтів, але їх не називають багатобайтовими літералами char. Бути менш зарозумілим допомогло б вам бути точнішими самі.
Blaisorblade

@Blaisorblade Широкі літерали символів тут не доречні - вони не мають нічого спільного з тим, що я написав. Я був точний, і вам не вистачає розуміння, і ваша хитра спроба виправити мене - це те, що зарозуміло.
Джим Балтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.