Подтвердить что ты не робот

Что заставляет char быть подписанным или неподписанным при использовании gcc?

Что вызывает, если char в C (с использованием gcc) подписан или без знака? Я знаю, что стандарт не диктует один над другим и что я могу проверять CHAR_MIN и CHAR_MAX на limits.h, но я хочу знать, какие триггеры срабатывают друг над другом при использовании gcc

Если я читаю limit.h из libgcc-6, я вижу, что есть макрос __CHAR_UNSIGNED__, который определяет "default" char подписанный или неподписанный, но я не уверен, установлен ли это компилятором в (его ) построенное время.

Я попытался перечислить предопределенные макросы GCC с помощью

$ gcc -dM -E -x c /dev/null | grep -i CHAR
#define __UINT_LEAST8_TYPE__ unsigned char
#define __CHAR_BIT__ 8
#define __WCHAR_MAX__ 0x7fffffff
#define __GCC_ATOMIC_CHAR_LOCK_FREE 2
#define __GCC_ATOMIC_CHAR32_T_LOCK_FREE 2
#define __SCHAR_MAX__ 0x7f
#define __WCHAR_MIN__ (-__WCHAR_MAX__ - 1)
#define __UINT8_TYPE__ unsigned char
#define __INT8_TYPE__ signed char
#define __GCC_ATOMIC_WCHAR_T_LOCK_FREE 2
#define __CHAR16_TYPE__ short unsigned int
#define __INT_LEAST8_TYPE__ signed char
#define __WCHAR_TYPE__ int
#define __GCC_ATOMIC_CHAR16_T_LOCK_FREE 2
#define __SIZEOF_WCHAR_T__ 4
#define __INT_FAST8_TYPE__ signed char
#define __CHAR32_TYPE__ unsigned int
#define __UINT_FAST8_TYPE__ unsigned char

но не смог найти __CHAR_UNSIGNED__

Фон: у меня есть код, который я компилирую на двух разных машинах:

Настольный ПК:

  • Debian GNU/Linux 9.1 (растяжка)
  • gcc версия 6.3.0 20170516 (Debian 6.3.0-18)
  • Intel (R) Core (TM) i3-4150
  • libgcc-6-dev: 6.3.0-18
  • char подписан

Малина Pi3:

  • Raspbian GNU/Linux 9.1 (растяжка)
  • gcc версия 6.3.0 20170516 (Raspbian 6.3.0-18 + rpi1)
  • ARMv7 Processor rev 4 (v7l)
  • libgcc-6-dev: 6.3.0-18 + rpi
  • char не указано

Таким образом, единственным очевидным отличием является архитектура процессора...

4b9b3361

Ответ 1

В соответствии со стандартом C11 (прочитайте n1570), char может быть signed или unsigned (так что у вас на самом деле есть два варианта C). Что конкретно это касается конкретной реализации.

Некоторые процессоры и архитектуры набора инструкций или двоичные интерфейсы приложений поддерживает тип символа (байта) signed (например, потому что он хорошо отображает некоторые машинный код), другие предпочитают unsigned.

gcc имеет даже некоторые -fsigned-char или -funsigned-char option, которые вы почти никогда не должны использовать (потому что изменение этого прерывает некоторые угловые случаи в соглашения о вызовах и ABI), если вы не перекомпилируете все, включая C стандартная библиотека.

Вы можете использовать feature_test_macros (7) и <endian.h> (см. endian (3)) или autoconf в Linux, чтобы определить, что ваша система имеет.

В большинстве случаев вам следует написать portable код C, который не зависит от этих вещей. И вы можете найти межплатформенные библиотеки (например, glib), чтобы помочь вам в этом.

BTW gcc -dM -E -x c /dev/null также предоставляет __BYTE_ORDER__ и т.д., и если вы хотите 8-битный байт без знака, вы должны использовать <stdint.h> и его uint8_t (более портативный и более читаемый). Стандартная limits.h определяет CHAR_MIN и SCHAR_MIN и CHAR_MAX и SCHAR_MAX (вы можете сравнить их для равенства для обнаружения signed char) и т.д.

Кстати, вы должны заботиться о кодировке символов, но сегодня большинство систем используют UTF-8 везде. Библиотеки, такие как libunistring, полезны. См. Также this и помните, что на самом деле символ Unicode, закодированный в UTF-8 может охватывать несколько байтов (т.е. char -s).

Ответ 2

Значение по умолчанию зависит от платформы и собственного набора кодов. Например, машины, которые обычно используют EBCDIC (обычно мэйнфреймы), должны использовать unsigned char (или иметь CHAR_BIT > 8), поскольку для стандарта C требуется, чтобы символы в базовом кодовом наборе были положительными, а EBCDIC использует коды, например 240 для цифры 0. (Стандарт C11, §6.2.5 Типы ¶2 говорит: Объект, объявленный как тип char, достаточно велик для хранения любого члена базового набора символов выполнения. Если хранится элемент набора основных символов выполнения в объекте char его значение гарантировано неотрицательно.)

Вы можете контролировать, какой знак GCC использует с параметрами -fsigned-char или -funsigned-char. Разве это хорошая идея - это отдельное обсуждение.

Ответ 3

Тип символа char должен быть signed или unsigned, в зависимости от платформы и компилятора.

В соответствии с эта ссылка:

Стандарты C и С++ позволяют использовать тип символа char или без знака, в зависимости от платформы и компилятора.

Большинство систем, включая x86 GNU/Linux и Microsoft Windows, используют подписанные char,

но на основе процессоров PowerPC и ARM обычно используются неподписанные char. (29)

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

GCC предоставляет параметры -fsigned-char и -funsigned-char для установки типа char по умолчанию.

Ответ 4

gcc имеет две опции времени компиляции, которые управляют поведением char:

-funsigned-char
-fsigned-char

Не рекомендуется использовать какие-либо из этих параметров, если вы точно не знаете, что делаете.

Значение по умолчанию зависит от платформы и фиксируется при построении самой gcc. Он выбран для лучшей совместимости с другими инструментами, которые существуют на этой платформе.

Источник.

Ответ 5

На x86-64 Linux, по крайней мере, он определяется x86-64 System V psABI

Другие платформы будут иметь аналогичные документы стандартов ABI, которые определяют правила, позволяющие различным компиляторам C согласовывать друг с другом при вызове соглашений, структурных макетов и тому подобных. (См. теги wiki для ссылок на другие документы x86 ABI или другие места для других архитектур. Большинство архитектур, отличных от x86, имеют только один или два стандартных ABI.)

Из x86-64 SysV ABI: Рисунок 3.1: Скалярные типы

   C            sizeof      Alignment       AMD64
                            (bytes)         Architecture

_Bool*          1             1              boolean
-----------------------------------------------------------
char            1             1              signed byte
signed char
---------------------------------------------------------
unsigned char   1             1              unsigned byte
----------------------------------------------------------
...
-----------------------------------------------------------
int             4             4              signed fourbyte
signed int
enum***
-----------------------------------------------------------
unsigned int    4             4              unsigned fourbyte
--------------------------------------------------------------
...

* Этот тип называется bool в С++.

*** С++ и некоторые реализации разрешений C разрешают больше, чем внутр. Базовый тип наложен на unsigned int, long int или unsigned long int в этом порядке.


Независимо от того, подписан ли char или нет, на самом деле непосредственно влияет на соглашение о вызове в этом случае из-за недокументированного в настоящее время требования, на которое полагается clang: узкие типы являются знаками или нолями -расширен до 32 бит при передаче как функции args, в соответствии с прототипом вызываемого абонента.

Итак, для int foo(char c) { return c; }, clang будет полагаться на вызывающего, чтобы иметь расширенный знак arg. (code + asm для этого и вызывающий на Godbolt).

gcc:
    movsx   eax, dil       # sign-extend low byte of first arg reg into eax
    ret

clang:
    mov     eax, edi       # copy whole 32-bit reg
    ret

Даже несмотря на соглашение о вызове, компиляторы C должны соглашаться, поэтому они компилируют встроенные функции в .h тем же способом.

Если (int)(char)x вел себя по-разному в разных компиляторах для одной и той же платформы, они не были бы действительно совместимыми.

Ответ 6

Важным практическим замечанием является то, что тип строкового литерала UTF-8, такого как u8"...", представляет собой массив char, и он должен храниться в формате UTF-8. Символы в базовом наборе гарантированно эквивалентны положительным целым числам. Тем не менее,

Если какой-либо другой символ хранится в объекте char, результирующее значение определяется реализацией, но должно находиться в пределах диапазона значений, которые могут быть представлены в этом типе.

(В С++ тип строковой константы UTF-8 равен const char [], и не указано, имеют ли символы вне базового набора числовые представления вообще.)

Поэтому, если вашей программе нужно свернуть биты строки UTF-8, вам нужно будет использовать unsigned char. В противном случае любой код, который проверяет, находятся ли байты строки UTF-8 в определенном диапазоне, не будет переносимым.

Лучше явно указать на unsigned char*, чем писать char, и ожидать, что программист скомпилируется с правильными настройками, чтобы настроить его как unsigned char. Однако вы можете использовать static_assert() для проверки того, включает ли диапазон char все числа от 0 до 255.