incorrectly - multi character character constant c
¿Qué causa que un char esté firmado o no cuando usa gcc? (6)
De acuerdo con el estándar C11 (leer n1570 ), char
puede ser signed
o unsigned
(por lo que en realidad tiene dos sabores de C). Lo que es exactamente es una implementación específica.
Algunos processors y arquitecturas de conjuntos de instrucciones o interfaces binarias de aplicaciones favorecen un tipo de caracteres con signed
(byte) (p. Ej., Porque se ajusta muy bien a algunas instrucciones de código de máquina ), otros favorecen a uno unsigned
.
gcc
tiene incluso alguna -fsigned-char
o -funsigned-char
que casi nunca debería usar (porque cambiarla rompe algunos casos de esquina en convenciones de llamadas y ABI) a menos que recompile todo, incluida su biblioteca estándar C.
Puede usar feature_test_macros(7) y <endian.h>
(ver endian(3) ) o autoconf en Linux para detectar lo que tiene su sistema.
En la mayoría de los casos, debe escribir código C portable , que no depende de esas cosas. Y puede encontrar bibliotecas multiplataforma (por ejemplo, glib ) para ayudarlo en eso.
BTW gcc -dM -E -xc /dev/null
también da __BYTE_ORDER__
etc, y si quiere un byte de 8 bits sin signo, debe usar <stdint.h>
y su uint8_t
(más portátil y más legible). Y los CHAR_MIN
estándar.h definen CHAR_MIN
y SCHAR_MIN
y CHAR_MAX
y SCHAR_MAX
(se pueden comparar por igualdad para detectar implementaciones de SCHAR_MAX
signed char
), etc ...
Por cierto, debería preocuparse por la codificación de caracteres , pero la mayoría de los sistemas actuales usan UTF-8 en todas partes . Las bibliotecas como libunistring son útiles. Vea también this y recuerde que, hablando en términos prácticos, un carácter Unicode codificado en UTF-8 puede abarcar varios bytes (es decir, char
-s).
¿Qué causa si un char
en C (usando gcc) está firmado o no? Sé que el estándar no dicta uno sobre el otro y que puedo verificar CHAR_MIN
y CHAR_MAX
desde limits.h, pero quiero saber qué provoca uno sobre el otro cuando se usa gcc
Si leo limits.h desde libgcc-6, veo que hay una macro __CHAR_UNSIGNED__
que define un carácter "predeterminado" con o sin signo, pero no estoy seguro de que el compilador lo configure en (su) hora incorporada.
Intenté enumerar los makros predefinidos de GCC con
$ 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
pero no pudo encontrar __CHAR_UNSIGNED__
Antecedentes: tengo un código que compilo en dos máquinas diferentes:
Computadora de escritorio:
- Debian GNU / Linux 9.1 (estirar)
- gcc versión 6.3.0 20170516 (Debian 6.3.0-18)
- Intel (R) Core (TM) i3-4150
- libgcc-6-dev: 6.3.0-18
-
char
está firmado
Raspberry Pi3 :
- Raspbian GNU / Linux 9.1 (estirar)
- gcc versión 6.3.0 20170516 (Raspbian 6.3.0-18 + rpi1)
- Procesador ARMv7 rev 4 (v7l)
- libgcc-6-dev: 6.3.0-18 + rpi
-
char
no tiene firma
Entonces, la única diferencia obvia es la arquitectura de la CPU ...
El valor predeterminado depende de la plataforma y el conjunto de códigos nativo. Por ejemplo, las máquinas que usan EBCDIC (mainframes por lo general) deben usar unsigned char
(o tener CHAR_BIT > 8
) porque el estándar C requiere caracteres en el conjunto de códigos básico para ser positivo, y EBCDIC usa códigos como 240 para dígito 0. (C11 estándar, §6.2.5 Tipos ¶2 dice: Un objeto declarado como tipo char
es lo suficientemente grande como para almacenar cualquier miembro del conjunto de caracteres de ejecución básico. Si un miembro del conjunto de caracteres de ejecución básica se almacena en un objeto char
, se garantiza su valor a ser no negativo. )
Puede controlar qué signo utiliza GCC con las -fsigned-char
o -funsigned-char
. Si esa es una buena idea es una discusión por separado.
En x86-64 Linux al menos, está definido por el sistema x86-64 V psABI
Otras plataformas tendrán documentos de estándares ABI similares que especifiquen las reglas que permiten que diferentes compiladores de C estén de acuerdo entre sí en convenciones de llamadas, diseños de estructuras y cosas por el estilo. (Consulte la wiki de la etiqueta x86 para ver los enlaces a otros documentos ABI x86 u otros lugares para otras arquitecturas. La mayoría de las arquitecturas distintas de x86 tienen solo uno o dos ABI estándar).
Del x86-64 SysV ABI: Figura 3.1: Tipos escalares
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 -------------------------------------------------------------- ...
* Este tipo se llama
bool
en C ++.*** C ++ y algunas implementaciones de C permiten enum mayores que un int. El tipo subyacente se ejecuta en un int sin signo, int largo o int largo sin signo, en ese orden.
Si char
está firmado o no realmente afecta directamente a la convención de llamadas en este caso, debido a un requisito actualmente no documentado en el que clang se basa: los tipos estrechos son de signo o cero extendidos a 32 bits cuando pasan como argumentos de función , de acuerdo con el destinatario. prototipo.
Entonces para int foo(char c) { return c; }
int foo(char c) { return c; }
, clang se basará en que la persona que llama haya extendido el signo arg. ( código + asm para esto y un llamador en 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
Incluso aparte de la convención de llamadas, los compiladores de C tienen que aceptar, por lo que compilan las funciones en línea en una .h
la misma manera.
Si (int)(char)x
comportara de manera diferente en diferentes compiladores para la misma plataforma, en realidad no serían compatibles.
Tipo de char
para signed
o unsigned
, según la plataforma y el compilador.
De acuerdo con this enlace de referencia:
Los estándares C y C ++ permiten que el tipo de carácter char sea firmado o no , dependiendo de la plataforma y el compilador .
La mayoría de los sistemas, incluidos x86 GNU / Linux y Microsoft Windows, utilizan char firmado ,
pero aquellos basados en procesadores PowerPC y ARM típicamente usan caracteres sin signo . (29)
Esto puede conducir a resultados inesperados al portar programas entre plataformas que tienen diferentes valores predeterminados para el tipo de char.
GCC proporciona las opciones -fsigned-char
y -funsigned-char
para establecer el tipo predeterminado de char
.
Una nota práctica importante es que el tipo de un literal de cadena UTF-8, como u8"..."
, es una matriz de caracteres, y debe almacenarse en formato UTF-8. Se garantiza que los caracteres en el conjunto básico sean equivalentes a enteros positivos. Sin embargo,
Si cualquier otro carácter se almacena en un objeto char, el valor resultante está definido por la implementación, pero debe estar dentro del rango de valores que se pueden representar en ese tipo.
(En C ++, el tipo de la constante de cadena UTF-8 es const char []
y no se especifica si los caracteres fuera del conjunto básico tienen representaciones numéricas).
Por lo tanto, si su programa necesita mezclar los bits de una cadena UTF-8, necesitaría usar unsigned char
. De lo contrario, cualquier código que verifique si los bytes de una cadena UTF-8 están en un cierto rango no será portátil.
Es mejor emitir explícitamente a unsigned char*
que escribir char
y esperar que el programador compile con la configuración correcta para configurarlo como unsigned char
. Sin embargo, puede usar static_assert()
para probar si el rango de char
incluye todos los números de 0 a 255.
gcc tiene dos opciones de tiempo de compilación que controlan el comportamiento de char
:
-funsigned-char
-fsigned-char
No se recomienda utilizar ninguna de estas opciones a menos que sepa exactamente lo que está haciendo.
El valor predeterminado depende de la plataforma y se corrige cuando se genera gcc. Se elige para la mejor compatibilidad con otras herramientas que existen en esa plataforma.