uint8_t vs unsigned char

231

¿Cuál es la ventaja de usar uint8_tmás unsigned charen C?

Sé que en casi todos los sistemas uint8_tes solo una definición de tipo unsigned char, entonces, ¿por qué usarlo?

Lyndon White
fuente

Respuestas:

225

Documenta su intención: almacenará números pequeños, en lugar de un personaje.

También se ve mejor si está usando otros typedefs como uint16_to int32_t.

Mark Ransom
fuente
1
No estaba claro en la pregunta original si estábamos hablando de un tipo estándar o no. Estoy seguro de que ha habido muchas variaciones de esta convención de nomenclatura a lo largo de los años.
Mark Ransom
8
El uso explícito unsigned charo signed chardocumenta la intención también, ya que sin adornos chares lo que muestra que estás trabajando con personajes.
caf
99
Pensé que un sin adornos unsignedera unsigned intpor definición?
Mark Ransom el
55
@endolith, usar uint8_t para una cadena no es necesariamente incorrecto, pero definitivamente es extraño.
Mark Ransom
55
@endolith, creo que puedo hacer un caso para uint8_t con texto UTF8. De hecho, charparece implicar un carácter, mientras que en el contexto de una cadena UTF8, puede ser solo un byte de un carácter multibyte. El uso de uint8_t podría dejar en claro que no se debe esperar un carácter en cada posición; en otras palabras, cada elemento de la cadena / matriz es un entero arbitrario sobre el que no se deben hacer suposiciones semánticas. Por supuesto, todos los programadores de C lo saben, pero puede empujar a los principiantes a hacer las preguntas correctas.
TNE
70

Solo para ser pedante, algunos sistemas pueden no tener un tipo de 8 bits. De acuerdo con Wikipedia :

Se requiere una implementación para definir tipos enteros de ancho exacto para N = 8, 16, 32 o 64 si y solo si tiene algún tipo que cumpla con los requisitos. No es necesario definirlos para ninguna otra N, incluso si admite los tipos apropiados.

Por uint8_tlo tanto, no se garantiza que exista, aunque lo hará para todas las plataformas donde 8 bits = 1 byte. Algunas plataformas integradas pueden ser diferentes, pero eso se está volviendo muy raro. Algunos sistemas pueden definir chartipos de 16 bits, en cuyo caso probablemente no habrá ningún tipo de 8 bits.

Aparte de ese problema (menor), la respuesta de @Mark Ransom es la mejor en mi opinión. Utilice el que muestre más claramente para qué está utilizando los datos.

Además, supongo que usted quiso decir uint8_t(el tipo de definición estándar de C99 proporcionado en el stdint.hencabezado) en lugar de uint_8(no forma parte de ningún estándar).

Chris Lutz
fuente
3
@caf, por pura curiosidad, ¿puede vincular a la descripción de algunos? Sé que existen porque alguien mencionó uno (y lo vinculó a los documentos del desarrollador) en una discusión moderada comp.lang.c ++ sobre si las garantías de tipo C / C ++ son demasiado débiles, pero ya no puedo encontrar ese hilo, y siempre es útil. hacer referencia a eso en cualquier discusión similar :)
Pavel Minaev
3
"Algunos sistemas pueden definir tipos de caracteres como 16 bits, en cuyo caso probablemente no habrá ningún tipo de 8 bits". - y a pesar de algunas objeciones incorrectas de mi parte, Pavel ha demostrado en su respuesta que si char tiene 16 bits, incluso si el compilador proporciona un tipo de 8 bits, no debe llamarlo uint8_t(o escribirlo para eso). Esto se debe a que el tipo de 8 bits tendría bits no utilizados en la representación de almacenamiento, que uint8_tno debe tener.
Steve Jessop el
3
La arquitectura SHARC tiene palabras de 32 bits. Ver en.wikipedia.org/wiki/… para más detalles.
BCran el
2
Y los DSP C5000 de TI (que estaban en OMAP1 y OMAP2) son de 16 bits. Creo que para OMAP3 fueron a la serie C6000, con un carácter de 8 bits.
Steve Jessop
44
Excavando en N3242 - "Borrador de trabajo, estándar para el lenguaje de programación C ++", la sección 18.4.1 <cstdint> sinopsis dice: typedef unsigned integer type uint8_t; // optional Entonces, en esencia, no se necesita una biblioteca conforme estándar C ++ para definir uint8_t (ver el comentario // opcional )
nightlytrails
43

El punto es escribir código independiente de la implementación. unsigned charno se garantiza que sea un tipo de 8 bits. uint8_tes (si está disponible).

Hormiga
fuente
44
... si existe en un sistema, pero eso será muy raro. +1
Chris Lutz
2
bueno, si realmente tuvo problemas con su código al no compilarse en un sistema porque uint8_t no existía, podría usar find y sed para cambiar automáticamente todas las apariciones de uint8_t a caracteres sin signo o algo más útil para usted.
bazz
2
@bazz: no si está suponiendo que es un tipo de 8 bits que no puede, por ejemplo, para desempaquetar los datos empaquetados de manera byte por un sistema remoto. La suposición implícita es que la razón por la que uint8_t no existe es en un procesador donde un carácter tiene más de 8 bits.
Chris Stratton
tirar en aserción asir (sizeof (unsigned char) == 8);
bazz
3
@bazz afirmación incorrecta, me temo. sizeof(unsigned char)regresará 1por 1 byte. pero si un sistema char e int son del mismo tamaño, por ejemplo, de 16 bits sizeof(int), también volverá1
Toby
7

Como dijiste, " casi todos los sistemas".

chares probablemente uno de los menos propensos a cambiar, pero una vez que comience a usar uint16_ty amigos, usar las uint8_tmezclas mejor, e incluso puede ser parte de un estándar de codificación.

Sólo en el amor
fuente
7

En mi experiencia, hay dos lugares donde queremos usar uint8_t para significar 8 bits (y uint16_t, etc.) y donde podemos tener campos de menos de 8 bits. Ambos lugares son donde el espacio es importante y, a menudo, necesitamos mirar un volcado sin procesar de los datos al depurar y debemos poder determinar rápidamente qué representa.

El primero está en los protocolos de RF, especialmente en los sistemas de banda estrecha. En este entorno, es posible que necesitemos empaquetar tanta información como podamos en un solo mensaje. El segundo es en el almacenamiento flash, donde podemos tener un espacio muy limitado (como en los sistemas integrados). En ambos casos, podemos usar una estructura de datos empaquetados en la que el compilador se encargará del empaque y desempaquetado por nosotros:

#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

El método que use depende de su compilador. Es posible que también deba admitir varios compiladores diferentes con los mismos archivos de encabezado. Esto sucede en sistemas integrados donde los dispositivos y servidores pueden ser completamente diferentes, por ejemplo, puede tener un dispositivo ARM que se comunica con un servidor Linux x86.

Hay algunas advertencias sobre el uso de estructuras empaquetadas. El mayor problema es que debe evitar desreferenciar la dirección de un miembro. En sistemas con palabras mutibyte alineadas, esto puede resultar en una excepción desalineada, y un coredump.

Algunas personas también se preocuparán por el rendimiento y argumentarán que el uso de estas estructuras empaquetadas ralentizará su sistema. Es cierto que, detrás de escena, el compilador agrega código para acceder a los miembros de datos no alineados. Puede ver eso mirando el código de ensamblaje en su IDE.

Pero dado que las estructuras empaquetadas son más útiles para la comunicación y el almacenamiento de datos, los datos se pueden extraer en una representación no empaquetada cuando se trabaja con ellos en la memoria. Normalmente no necesitamos trabajar con todo el paquete de datos en la memoria de todos modos.

Aquí hay una discusión relevante:

pragma pack (1) ni __attribute__ ((alineado (1))) funciona

¿El __attribute __ ((empaquetado)) / #pragma pack de gcc no es seguro?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html

Tereus Scott
fuente
6

Hay poco. Desde el punto de vista de la portabilidad, charno puede ser menor que 8 bits, y nada puede ser menor que char, por lo que si una implementación C dada tiene un tipo entero de 8 bits sin signo, será char. Alternativamente, es posible que no tenga ninguno, en cuyo punto cualquier typedeftruco es discutible.

Podría usarse para documentar mejor su código en el sentido de que está claro que necesita bytes de 8 bits y nada más. Pero en la práctica, es una expectativa razonable prácticamente en cualquier lugar (hay plataformas DSP en las que no es cierto, pero las posibilidades de que su código se ejecute allí son escasas, y también podría equivocarse usando una afirmación estática en la parte superior de su programa en tal plataforma).

Pavel Minaev
fuente
77
@Skizz: No, el estándar requiere unsigned charpoder mantener valores entre 0 y 255. Si puedes hacerlo en 4 bits, no tengo ganas.
Chris Lutz el
1
"sería un poco más engorroso" - engorroso en el sentido de que tendrías que caminar (nadar, tomar un avión, etc.) hasta donde estaba el escritor del compilador, golpearlos en la parte posterior de la cabeza y hacer que se agreguen uint8_ta la implementación. Me pregunto, ¿los compiladores para DSP con caracteres de 16 bits suelen implementarse uint8_to no?
Steve Jessop
66
Por cierto, pensándolo bien, es quizás la forma más directa de decir "Realmente necesito 8 bits" #include <stdint.h>, y usar uint8_t. Si la plataforma lo tiene, se lo dará. Si la plataforma no lo tiene, su programa no se compilará, y la razón será clara y directa.
Pavel Minaev
2
Todavía no hay cigarro, lo siento: "Para los tipos enteros sin signo distintos de caracteres sin signo, los bits de la representación del objeto se dividirán en dos grupos: bits de valor y bits de relleno ... Si hay N bits de valor, cada bit representará un valor diferente potencia de 2 entre 1 y 2 ^ (N-1), de modo que los objetos de ese tipo serán capaces de representar valores de 0 a 2 ^ (N-1) utilizando una representación binaria pura ... El nombre typedef intN_t designa un tipo entero con signo con ancho N, sin bits de relleno y una representación de complemento a dos ".
Pavel Minaev el
1
Si solo necesita un módulo aritmético, un campo de bits sin signo funcionará bien (si es inconveniente). Es cuando necesitas, digamos, una variedad de octetos sin relleno, es cuando estás SOL. La moraleja de la historia no es codificar DSP y apegarse a arquitecturas de caracteres de 8 bits adecuadas y honestas para Dios :)
Pavel Minaev
4

Eso es realmente importante, por ejemplo, cuando está escribiendo un analizador de red. los encabezados de paquetes se definen por la especificación del protocolo, no por la forma en que funciona el compilador C de una plataforma en particular.

VP.
fuente
Cuando pregunté esto, estaba definiendo un protocolo simple para la comunicación sobre serie.
Lyndon White
2

En casi todos los sistemas he conocido uint8_t == unsigned char, pero esto no está garantizado por el estándar C. Si está intentando escribir código portátil y importa exactamente el tamaño de la memoria, use uint8_t. De lo contrario, use char sin firmar.

atlpeg
fuente
3
uint8_t siempre coincide con el rango y el tamaño unsigned chary el relleno (ninguno) cuando unsigned char es de 8 bits. Cuando unsigned charno es de 8 bits, uint8_tno existe.
chux
@chux, ¿tiene una referencia al lugar exacto en el estándar donde dice eso? Si unsigned charestá 8 bits, se uint8_tgarantiza que sea una typedefde la misma y no una typedefde un tipo entero sin signo extendido ?
hsivonen
@hsivonen "lugar exacto en el estándar donde dice eso?" -> No - todavía mira a 7.20.1.1. Se deduce fácilmente como unsigned char/signed char/charel tipo más pequeño, no más pequeño que 8 bits. unsigned charNo tiene relleno. Para uint8_tser, debe ser de 8 bits, sin relleno, debido a un tipo entero proporcionado por la implementación: que coincida con los requisitos mínimos de unsigned char. En cuanto a "... garantizado para ser un typedef ..." parece una buena pregunta para publicar.
chux - Restablece a Monica