Разница между char и signed char в с ++?

Рассмотрим следующий код:

#include <iostream>
#include <type_traits>

int main(int argc, char* argv[])
{
    std::cout<<"std::is_same<int, int>::value = "<<std::is_same<int, int>::value<<std::endl;
    std::cout<<"std::is_same<int, signed int>::value = "<<std::is_same<int, signed int>::value<<std::endl;
    std::cout<<"std::is_same<int, unsigned int>::value = "<<std::is_same<int, unsigned int>::value<<std::endl;
    std::cout<<"std::is_same<signed int, int>::value = "<<std::is_same<signed int, int>::value<<std::endl;
    std::cout<<"std::is_same<signed int, signed int>::value = "<<std::is_same<signed int, signed int>::value<<std::endl;
    std::cout<<"std::is_same<signed int, unsigned int>::value = "<<std::is_same<signed int, unsigned int>::value<<std::endl;
    std::cout<<"std::is_same<unsigned int, int>::value = "<<std::is_same<unsigned int, int>::value<<std::endl;
    std::cout<<"std::is_same<unsigned int, signed int>::value = "<<std::is_same<unsigned int, signed int>::value<<std::endl;
    std::cout<<"std::is_same<unsigned int, unsigned int>::value = "<<std::is_same<unsigned int, unsigned int>::value<<std::endl;
    std::cout<<"----"<<std::endl;
    std::cout<<"std::is_same<char, char>::value = "<<std::is_same<char, char>::value<<std::endl;
    std::cout<<"std::is_same<char, signed char>::value = "<<std::is_same<char, signed char>::value<<std::endl;
    std::cout<<"std::is_same<char, unsigned char>::value = "<<std::is_same<char, unsigned char>::value<<std::endl;
    std::cout<<"std::is_same<signed char, char>::value = "<<std::is_same<signed char, char>::value<<std::endl;
    std::cout<<"std::is_same<signed char, signed char>::value = "<<std::is_same<signed char, signed char>::value<<std::endl;
    std::cout<<"std::is_same<signed char, unsigned char>::value = "<<std::is_same<signed char, unsigned char>::value<<std::endl;
    std::cout<<"std::is_same<unsigned char, char>::value = "<<std::is_same<unsigned char, char>::value<<std::endl;
    std::cout<<"std::is_same<unsigned char, signed char>::value = "<<std::is_same<unsigned char, signed char>::value<<std::endl;
    std::cout<<"std::is_same<unsigned char, unsigned char>::value = "<<std::is_same<unsigned char, unsigned char>::value<<std::endl;
    return 0;
}

Результат:

std::is_same<int, int>::value = 1
std::is_same<int, signed int>::value = 1
std::is_same<int, unsigned int>::value = 0
std::is_same<signed int, int>::value = 1
std::is_same<signed int, signed int>::value = 1
std::is_same<signed int, unsigned int>::value = 0
std::is_same<unsigned int, int>::value = 0
std::is_same<unsigned int, signed int>::value = 0
std::is_same<unsigned int, unsigned int>::value = 1
----
std::is_same<char, char>::value = 1
std::is_same<char, signed char>::value = 0
std::is_same<char, unsigned char>::value = 0
std::is_same<signed char, char>::value = 0
std::is_same<signed char, signed char>::value = 1
std::is_same<signed char, unsigned char>::value = 0
std::is_same<unsigned char, char>::value = 0
std::is_same<unsigned char, signed char>::value = 0
std::is_same<unsigned char, unsigned char>::value = 1 

Это означает, что int и signed int считаются одним типом, но не char и signed char. Это почему ?

И если я могу преобразовать char в signed char с помощью make_signed, как сделать обратное (преобразовать signed char в char)?


person Vincent    schedule 12.05.2013    source источник
comment
Интересно, что я знал, что char может быть подписанным или неподписанным, но я думал, что это будет по крайней мере эквивалентно одному из них.   -  person chris    schedule 12.05.2013


Ответы (5)


Это по замыслу стандарт C ++ говорит, что char, signed char и unsigned char относятся к разным типам. Я думаю, вы можете использовать статическое приведение для трансформации.

person Sergi0    schedule 12.05.2013

Существует три различных основных типа символов: char, signed char и unsigned char. Хотя существует три типа символов, существует только два представления: знаковый и беззнаковый. (Обычный) char использует одно из этих представлений. Какое из двух других представлений символов эквивалентно char , зависит от компилятора.

В беззнаковом типе все биты представляют значение. Например, 8-битный unsigned char может содержать значения от 0 до 255 включительно.

Стандарт не определяет, как представлены типы со знаком, но указывает, что диапазон должен быть равномерно разделен между положительными и отрицательными значениями. Следовательно, 8-битный знаковый символ гарантированно сможет хранить значения от -127 до 127.


Итак, как решить, какой тип использовать?

Вычисления с использованием char обычно проблематичны. Char по умолчанию подписан на некоторых машинах и неподписан на других. Поэтому мы не должны использовать (простой) char в арифметических выражениях. Используйте его только для удержания символов. Если вам нужно крошечное целое число, явно укажите либо signed char, либо unsigned char.

Выдержки взяты из C ++ Primer 5th edition, стр. 66.

person Ankit Gupta    schedule 11.07.2015
comment
Я знаю, что этот пост был написан очень давно, но этот ответ идентичен абзацу во второй главе C ++ Primer. - person Mia Wang; 25.08.2017
comment
@JerieWang Я отредактировал, это фактически дословно C ++ Primer. - person Nicolas; 28.01.2021

Действительно, Стандарт точно говорит, что char, signed char и unsigned char - это 3 разных типа. Символ обычно состоит из 8 бит, но это не налагается стандартом. 8-битное число может закодировать 256 уникальных значений; разница только в том, как интерпретируются эти 256 уникальных значений. Если вы рассматриваете 8-битное значение как двоичное значение со знаком, оно может представлять целочисленные значения от -128 (код 80H) до +127. Если вы считаете его беззнаковым, он может представлять значения от 0 до 255. По стандарту C ++ подписанный char гарантированно может содержать значения от -127 до 127 (не -128!), Тогда как беззнаковый char может хранить значения. От 0 до 255.

При преобразовании char в int результат определяется реализацией! результат может, например, быть -55 или 201 в соответствии с машинной реализацией одиночного символа «É» (ISO 8859-1). Действительно, ЦП, хранящий символ в слове (16 бит), может хранить либо FFC9, либо 00C9, либо C900, либо даже C9FF (в представлениях с прямым и обратным порядком байтов). Явное приведение к подписанному или неподписанному char действительно гарантирует результат преобразования char в int.

person berhauz    schedule 27.10.2014
comment
Я думаю, что все 11111111 (0xFF) означает -1 для подписанного символа, а не -128. Пробовал на VS. - person Rick; 22.03.2018
comment
спасибо, что указали мне на эту ужасную ошибку. теперь исправлено в посте. Действительно, -128 - это 80H, а не FFH, который равен -1 ... действительно легко найти двоичное представление отрицательного значения. для 8 бит просто дополните его 256, (для n бит дополните его 2 exp n), например. для -1: 256 - 1 = 255 = FFH. для -5: 256 -5 = 251 = FBH, а -128 дает 256 - 128 = 128 = 80H ... можно поиграть со старым калькулятором Windows, установленным в режиме программиста. - person berhauz; 23.03.2018
comment
К сожалению, некоторые реализации выбрали менее интуитивно понятное значение по умолчанию для char, обрабатывая символы как от -128 до 127, а не от 0 до 255, хотя ни в одной кодовой таблице ANSI никогда не использовались отрицательные числа (ни какие-либо Unicode). Это приводит к эксплойтам и нарушениям доступа, когда люди используют символы в качестве индексов в массивах, потому что такой символ, как «Ä», обрабатывается как -42. - person Dwayne Robinson; 29.04.2021

Добавление дополнительной информации о диапазоне: Начиная с c ++ 20, значение -128 также гарантируется для подписанного char: P1236R0: Альтернативная формулировка для P0907R4 целых чисел со знаком - это дополнение до двух

Для каждого значения x целочисленного типа со знаком существует уникальное значение y соответствующего целочисленного типа без знака, такое что x конгруэнтно y по модулю 2N, и наоборот; все такие x и y имеют одинаковое представление.

[Сноска: Это также известно как представление дополнения до двух. ].
[Пример: значение -1 знакового типа конгруэнтно значению 2N-1 соответствующего беззнакового типа; представления одинаковы для этих значений. ]

Минимальное значение, которое должно поддерживаться реализацией для экспоненты диапазона каждого целочисленного типа со знаком, указано в таблице X.

Я любезно и болезненно (поскольку SO не поддерживает уценку для таблицы) переписал таблицу x ниже:

╔═════════════╦════════════════════════════╗  
║ Type        ║ Minimum range exponent N   ║  
╠═════════════╬════════════════════════════╣  
║ signed char ║        8                   ║  
║ short       ║       16                   ║  
║ int         ║       16                   ║  
║ long        ║       32                   ║  
║ long long   ║       64                   ║  
╚═════════════╩════════════════════════════╝  

Следовательно, знаковый char имеет 8 бит: от -2ⁿ⁻¹ до 2ⁿ⁻¹-1 (n равно 8).

Гарантированный диапазон от -128 до 127. Следовательно, когда дело доходит до диапазона, больше нет разницы между char и signed char.


О комментарии Кадоиса: Есть то, что говорится в стандарте, и есть реальность.
Проверка реальности с помощью программы ниже:

#include <stdio.h>

int main(void) {
    char c = -128;
    printf("%d\n", (int)c);
    printf("%d\n", (int)--c);
    return 0;
}

Вывод:

-128
127

Я бы также сказал, что подписанный char поможет другим программистам, а также, возможно, компилятору понять, что вы будете использовать значение char для выполнения арифметики указателя.

person Antonin GAVREL    schedule 30.04.2020
comment
Просто для вашего беспокойства: таблицы теперь поддерживаются - к сожалению, я не смог воспользоваться свободой редактирования, очередь заполнена. meta.stackexchange.com/q/356997/390859 Внимание, только знаковый символ находится в диапазоне от -127, а не -128 на 127 - рассмотрим другие ответы. - person Cadoiz; 24.03.2021
comment
Таблицы поддерживаются с ноября 2020 года, мой ответ - с апреля. И чтобы подтвердить, что значение подписанного символа колеблется только от -127 до 127, вам нужны доказательства. Есть ли у тебя? Да, вижу мою правку. - person Antonin GAVREL; 24.03.2021
comment
И то, и другое не означало оскорбления, просто как намек - плюс вы опустили ключевое слово signed перед char. На какой платформе вы тестировали код? Мои доказательства ограничены моим собственным опытом работы с MS VS для x86 и x64, но я не пробовал самый последний. Я полностью согласен с вашим мнением о предпочтении (un)signed char char для арифметики (или, возможно, всего, кроме реальных символов, таких как 'a' =. - person Cadoiz; 24.03.2021
comment
Я знаю, что тоже не хочу быть оскорбительным, моя точка зрения такова: покажите, пожалуйста, результат попытки вышеуказанной программы на вашей ОС и покажите мне результат. Код был протестирован на Ubuntu (18.04). - person Antonin GAVREL; 24.03.2021
comment
Небольшое примечание: wg21.cmeerw.net/cwg/issue1759 Я опубликую реальные доказательства как как только у меня будет на это время. - person Cadoiz; 24.03.2021
comment
Я с нетерпением жду этого :) - person Antonin GAVREL; 24.03.2021

После этого очень подробного обзора различий между char, signed char и unsigned char, который я очень ценю, я задаюсь вопросом:

Какой вопрос он должен был разрешить?

Зачем нужен стандарт C ++ для создания чего-то настолько сложного?

Каковы преимущества наличия символа, в котором вы не уверены, будет ли он подписан или нет?

Желая уйти от этого вопроса, я хотел бы предложить свой ответ на такое безумное решение:

Символ - это символ, его миссия - содержать персонажа настолько, насколько это возможно.

Если я не ошибаюсь, текстовая строка (например, const char * или даже char []) представляет текст в кодировке UTF-8.

Если мы объединим обе концепции, мы получим, что char должен содержать символы UTF-8 до того, что это позволяет. И, как мы знаем, больше всего, что может содержать char как отдельный символ UTF-8, - это один из кодов в диапазоне от 0x00 до 0x7E. За пределами этого диапазона символы UTF-8 состоят из более чем одного символа, а для краткости - 2, 3 и 4 символа. Это представление следует называть не только символами, но и кодовой точкой.

Независимо от того, 1, 2, 3 или 4 символа, эти кодовые точки не имеют знака, на самом деле крайний левый бит используется для кодирования заголовка кода UTF-8 и выражения, сколько байтов он содержит, или, также, чтобы указать, что char дополняет заголовок до завершения кодовой точки.

В конечном итоге не имеет значения, подписан символ или нет, важно то, как символ будет интерпретироваться в контексте текстовой строки.

person AmbarJ2009    schedule 19.04.2021