Как char может хранить два числа?

Дело в следующем: у меня кириллический символ "б". Запуск следующего кода:

int main() {
    char c;
    scanf("%c", &c);
    printf("%d\n", c);
    return 0;
}

Показывает -48. НО, когда я отлаживаю эту переменную c, она показывает следующее: -48 '\320'введите здесь описание изображения.

Так как же это работает? Является ли это указателем на массив 2-х длин? Или как он может хранить два числа?


person V. Dalechyn    schedule 05.12.2018    source источник
comment
Восьмеричные числа снова бьют.   -  person EOF    schedule 06.12.2018
comment
Никакой c не является символом и может хранить только 1 байт.   -  person Fredrik    schedule 06.12.2018
comment
@Fredrik A char может хранить минимум один байт.   -  person klutt    schedule 06.12.2018
comment
@Broman Нет. Байт - это единица памяти, char - это тип, способный представлять символ из базового набора символов реализации.   -  person EOF    schedule 06.12.2018
comment
@EOF Я не понимаю, как это противоречит тому, что я сказал.   -  person klutt    schedule 06.12.2018
comment
@Broman A char не хранит ничего. Объект что-то хранит, поэтому правильнее было бы сказать, что байт может хранить ровно одно char.   -  person EOF    schedule 06.12.2018
comment
Отладчик не показывает два числа. Он показывает два представления одного и того же значения, одно в десятичном виде, а другое в виде символа с управляющей последовательностью.   -  person Barmar    schedule 06.12.2018
comment
@EOF Хорошо, объект типа char может хранить как минимум один байт.   -  person klutt    schedule 06.12.2018
comment
@Broman Это предложение бессмысленно.   -  person EOF    schedule 06.12.2018
comment
@EOF Согласны ли вы с тем, что 1 байт = 8 бит?   -  person klutt    schedule 06.12.2018
comment
@ Броман Конечно нет. CHAR_BIT определяется реализацией и должно быть равно или больше 8.   -  person EOF    schedule 06.12.2018
comment
@EOF Я только что нашел это в документации. Не обращай внимания на все, что я сказал.   -  person klutt    schedule 06.12.2018
comment
@Broman: что касается стандарта, char в значительной степени является определением байта, каким бы большим он ни был. Вы двое смешиваете два определения: байт = октет (что обычно подразумевается в настоящее время в сообществе разработчиков программного обеспечения) и байт = минимальное адресуемое количество = char (что означают стандарты C и C++ для байта).   -  person Matteo Italia    schedule 06.12.2018


Ответы (2)


Переменная char может использоваться для хранения небольшого целого числа1 или символа (точнее, единицы кода) в какой-то не очень четко определенной кодировке, обычно основанной на ASCII. Здесь отладчик просто пытается быть полезным, отображая два (спорно) осмысленных представления содержимого c.


Давайте представим на мгновение, что вы на самом деле написали a вместо б; в этом случае отладчик напишет что-то вроде

c = {char} 97 'a'

потому что фактическое число, хранящееся в c, равно 97 и, декодированное как ASCII, соответствует букве a.

К сожалению, идея о том, что вы можете поместить все возможные символы в одно 8-битное значение char, полностью ошибочна, поэтому наиболее распространенная кодировка, используемая в настоящее время (UTF-8), которая используется на вашем компьютере, требует нескольких единицы кода (≈байты) для представления одной кодовой точки (≈логический символ) (более подробная информация в этом вопрос). В частности, б представляется строкой из двух байтов, а именно байтов 0xD0 и 0xB1.

C ничего не знает о UTF-8 или кодовых точках; если вы укажете от %c до scanf, он читается одним байтом, независимо от того, достаточно ли его для представления полной кодовой точки UTF-8 или нет. Таким образом, был прочитан только первый из этих байтов, а c содержит только значение 0xD0; 0xB1 все еще находится в буфере, но его еще предстоит прочитать.

Возвращаясь к отображаемому отладчиком значению, в первую очередь следует отметить, что на вашей платформе (как, к сожалению, и на многих платформах) стоит знак char. Следовательно, байт 0xD0 интерпретируется как значение со знаком как -48 (действительно, 0xD0 = 208, которое "обертывается" на 127; 208 - 256 = -48).

Что касается '\320': здесь отладчик хотел бы отобразить ASCII-представление этого значения; однако байт 0xD0 находится за пределами диапазона символов ASCII2, поэтому здесь он отображается с управляющей последовательностью. Вы можете быть знакомы с '\n' для представления символа новой строки или \0 для символа NUL; как правило, \, за которым следуют от одной до трех цифр в C, означает байт с соответствующим восьмеричным значением; 0320 действительно является восьмеричным для 208, которое является десятичным для 0xD0.

Итак, никакой загадки здесь нет: c по-прежнему содержит единственное значение (которое составляет всего лишь «половину» вашего символа); то, что вы видите, всего лишь два (одинаково неудобных) представления его содержания.


Примечания

  1. На большинстве платформ [-128, 127] или [0, 255], в зависимости от знака char (который, к сожалению, определяется реализацией).
  2. Действительно, UTF-8 расширяет ASCII, используя только байты с установленным старшим битом (не используемые ASCII) для своих многобайтовых последовательностей; это гарантирует, что они не могут быть неверно истолкованы для текста ASCII.
person Matteo Italia    schedule 05.12.2018

Кириллические символы [с использованием utf-8] являются многобайтовыми chars. Ваш "символ" в шестнадцатеричном формате - это строка/массив:

D0B1

Таким образом, вы не можете использовать %c для его получения. Вам нужно использовать %s:

#include <stdio.h>

int
main(void)
{
    char utf[1000];
    char *cp;

    scanf("%s", utf);
    printf("%s\n", utf);

    for (cp = utf;  *cp != 0;  ++cp)
        printf(" %2.2X",*cp & 0xFF);
    printf("\n");

    return 0;
}

Вот результат:

б
 D0 B1

ОБНОВЛЕНИЕ:

Итак, как этот char расположен в памяти? Может ли C сделать char 2-байтовым, когда дело доходит до кириллицы?

Прежде всего, см.: https://en.wikipedia.org/wiki/UTF-8< /а>

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

То, что вы называете кириллическим символом, utf-8 называет "кодовой точкой".

При помещении в текстовый файл кодовая точка становится многобайтовой последовательностью, как упоминалось выше.

scanf и printf ничего об этом не знают. Например, printf просто отправляет строку: XXXXXXX\0, где X может быть одиночным символом ASCII или частью многосимвольного кода.

Эмулятор терминала должен понять это и вывести правильный символ из набора шрифтов utf-8 [который содержит символы кириллицы, греческие символы, французские символы и т. д.]

Такие функции, как strlen и strcpy, только заботятся о завершающем символе 0x00 EOS. Таким образом, технически они работают и обычно могут передавать строку utf-8 так же легко, как и строку ASCII, поскольку EOS не зависит от этого.

Но strlen даст вам число char в строке. Например, в приведенном выше примере strlen вернет 2, потому что D0 и B1 считаются отдельными значениями char в массиве char.

И strchr [вероятно] не сработает. Вы, вероятно, захотите использовать strstr вместо utf-8.

Конечно, в нем есть только одна кодовая точка для кириллического символа, поэтому utf-8 функциям приходится обрабатывать массив по-разному. Например, при подсчете количества кодовых точек они должны видеть, что D0B1 — это одиночная кодовая точка, поэтому в результате получается один

Общее правило состоит в том, что ASCII (0x01-0x7F) отображается непосредственно на utf-8 как отдельные chars. Все, что имеет установленный старший бит (0x80), является частью многобайтовой кодовой точки utf-8. 0x40 используется для обозначения начального [крайнего левого] ​​байта последовательности. Все остальные байты в последовательности имеют вид (в битах): 10xxxxxx. Количество оставшихся байтов в последовательности обозначается количеством бит префикса 1 в начальном байте. В таблице ниже показано, как декодировать последовательность байтов (x обозначает бит, являющийся частью значения кодовой точки):

# of    Start       Remaining Bytes
bytes   Byte
1       0xxxxxxx
2       110xxxxx    10xxxxxx
3       1110xxxx    10xxxxxx    10xxxxxx
4       11110xxx    10xxxxxx    10xxxxxx    10xxxxxx

Таким образом, функция, поддерживающая utf-8, может обнаруживать и пропускать кодовые точки при сканировании в прямом или обратном направлении. И может различать две [или более] смежные многобайтовые кодовые точки.

person Craig Estey    schedule 05.12.2018
comment
Итак, как этот char расположен в памяти? Может ли C сделать char 2-байтовым, когда дело доходит до кириллицы? Означает ли это, что я могу назначить char c кириллицу б? Хотя это один байт? - person V. Dalechyn; 06.12.2018
comment
Ну, я на самом деле только что понял, как на самом деле работает scanf("%c"), он взял один байт из кириллицы б, которая на самом деле двухбайтная. Но что такое /320 тогда? - person V. Dalechyn; 06.12.2018