example - c++ ejemplos
¿Puedo convertir char sin firmar en char y viceversa? (6)
Quiero usar una función que espere datos como este:
void process(char *data_in, int data_len);
Así que solo está procesando algunos bytes realmente.
Pero me siento más cómodo trabajando con "unsigned char" cuando se trata de bytes en bruto (de alguna manera "se siente" más correcto para tratar con valores positivos de 0 a 255 solamente), por lo que mi pregunta es:
¿Puedo pasar siempre con seguridad un unsigned char *
a esta función?
En otras palabras:
- ¿Está garantizado que puedo convertir (cast) de forma segura entre char y unsigned char a voluntad, sin ninguna pérdida de información?
- ¿Puedo convertir (cast) de forma segura entre punteros a caracteres char y unsigned a voluntad, sin pérdida de información?
Bonus: ¿Es la respuesta igual en C y C ++?
La respuesta corta es sí, si utiliza un reparto explícito, pero para explicarlo en detalle, hay tres aspectos a considerar:
1) Legalidad de la conversión.
La conversión entre signed T*
unsigned T*
y signed T*
unsigned T*
(para algún tipo de T
) en cualquier dirección es generalmente posible porque el tipo de fuente primero se puede convertir en void *
(esta es una conversión estándar, §4.10), y el void *
se puede convertir al tipo de destino utilizando un static_cast
explícito (§5.2.9 / 13):
static_cast<unsigned char*>(static_cast<void *>(data_in))
Esto puede ser abreviado (§5.2.10 / 7) como
reinterpret_cast<unsigned char *>(data_in)
porque char
es un tipo de diseño estándar (§3.9.1 / 7,8 y §3.9 / 9) y la firma no cambia la alineación (§3.9.1 / 1). También se puede escribir como un reparto de estilo C:
(unsigned char *)(data_in)
Una vez más, esto funciona en ambos sentidos, desde unsigned*
a signed*
y viceversa. También existe la garantía de que si aplica este procedimiento de una manera y luego retrocede, el valor del puntero (es decir, la dirección a la que apunta) no habrá cambiado (§5.2.10 / 7).
Todo esto se aplica no solo a las conversiones entre signed char *
unsigned char *
y signed char *
unsigned char *
, sino también a char *
/ unsigned char *
y char *
/ signed char *
, respectivamente. ( char
, signed char
y unsigned char
son formalmente tres tipos distintos, §3.9.1 / 1.)
Para que quede claro, no importa cuál de los tres métodos de conversión usas, pero debes usar uno. El simple hecho de pasar el puntero no funcionará, ya que la conversión, si bien es legal, no es una conversión estándar, por lo que no se realizará de forma implícita (el compilador emitirá un error si lo intenta).
2) Bien definido del acceso a los valores.
¿Qué sucede si, dentro de la función, elimina la referencia al puntero, es decir, realiza *data_in
para recuperar un valor de glúteo para el carácter subyacente; ¿Está esto bien definido y es legal? La regla relevante aquí es la regla de aliasing estricto (§3.10 / 10):
Si un programa intenta acceder al valor almacenado de un objeto a través de un glvalue distinto de uno de los siguientes tipos, el comportamiento no está definido:
- [...]
- un tipo que es el tipo firmado o sin signo correspondiente al tipo dinámico del objeto,
- [...]
- un tipo
char
ounsigned char
.
Por lo tanto, el acceso a un signed char
(o char
) a través de un unsigned char*
(o char
) y viceversa no está prohibido por esta regla: debe poder hacer esto sin problemas.
3) Valores resultantes
Después de eliminar el indicador del tipo convertido, ¿podrá trabajar con el valor que obtiene? Es importante tener en cuenta que la conversión y desreferenciación del puntero descrito anteriormente equivale a reinterpretar (¡no cambiar!) El patrón de bits almacenado en la dirección del personaje. Entonces, ¿qué sucede cuando un patrón de bits para un carácter con signo se interpreta como el de un carácter sin signo (o viceversa)?
Cuando se pasa de no firmado a firmado, el efecto típico será que para valores entre 0 y 128 no pasa nada, y valores por encima de 128 se vuelven negativos. Similar a la inversa: cuando se pasa de firmado a no firmado, los valores negativos aparecerán como valores superiores a 128.
Pero este comportamiento no está realmente garantizado por el Estándar. Lo único que garantiza el Estándar es que para los tres tipos, char
, unsigned char
y signed char
, todos los bits (no necesariamente 8, por cierto) se utilizan para la representación del valor. Por lo tanto, si interpreta a uno como el otro, haga algunas copias y luego guárdelo en la ubicación original, puede estar seguro de que no habrá pérdida de información (según lo requiera), pero no necesariamente sabrá cuáles son los valores. en realidad significa (al menos no de una manera totalmente portátil).
Puede pasar un puntero a un tipo diferente de char
, pero es posible que deba emitirlo explícitamente. Se garantiza que los punteros son del mismo tamaño y los mismos valores. No habrá ninguna pérdida de información durante la conversión.
Si desea convertir char
en char
unsigned char
dentro de la función, simplemente asigne un valor char
a una variable unsigned char
o convierta el valor char
en unsigned char
.
Si necesita convertir caracteres unsigned char
a datos sin pérdida de datos, es un poco más difícil, pero aún posible:
#include <limits.h>
char uc2c(unsigned char c)
{
#if CHAR_MIN == 0
// char is unsigned
return c;
#else
// char is signed
if (c <= CHAR_MAX)
return c;
else
// ASSUMPTION 1: int is larger than char
// ASSUMPTION 2: integers are 2''s complement
return c - CHAR_MAX - 1 - CHAR_MAX - 1;
#endif
}
Esta función convertirá los caracteres unsigned char
en caracteres de tal manera que el valor devuelto se pueda volver a convertir al mismo valor unsigned char
que el parámetro.
Realmente necesitas ver el código para process()
para saber si puedes pasar con seguridad caracteres sin firmar. Si la función utiliza los caracteres como un índice en una matriz, entonces no, no puede usar datos sin firmar.
Sí, siempre puede convertir de char a uns sin signo y viceversa sin problemas. Si ejecuta el siguiente código y lo compara con una tabla ASCII (ref. http://www.asciitable.com/ ), puede ver una prueba por sí mismo y cómo C / C ++ maneja las conversiones, se ocupan de exactamente de la misma manera:
#include "stdio.h"
int main(void) {
//converting from char to unsigned char
char c = 0;
printf("%d byte(s)/n", sizeof(char)); // result: 1byte, i.e. 8bits, so there are 2^8=256 values that a char can store.
for (int i=0; i<256; i++){
printf("int value: %d - from: %c/tto: %c/n", c, c, (unsigned char) c);
c++;
}
//converting from unsigned char to char
unsigned char uc = 0;
printf("/n%d byte(s)/n", sizeof(unsigned char));
for (int i=0; i<256; i++){
printf("int value: %d - from: %c/tto: %c/n", uc, uc, (char) uc);
uc++;
}
}
¡No publicaré la salida porque tiene demasiadas líneas! Se puede observar en la salida que en la primera mitad de cada sección, es decir, de i = 0: 127, la conversión de caracteres a caracteres sin signo y viceversa funciona bien, sin ninguna modificación o pérdida.
Sin embargo, a partir de i = 128: 255, los caracteres y los caracteres sin signo no se pueden convertir, o tendría salidas diferentes, ya que los caracteres sin signo guardan los valores de [0: 256] y los valores en el intervalo [-128: 127 ]). Sin embargo, el comportamiento en esta segunda mitad es irrelevante, porque en C / C ++, en general, solo lleva con caracteres / caracteres sin signo como caracteres ASCII, que pueden tomar solo 128 valores diferentes y los otros 128 valores (positivo para caracteres o negativo para caracteres sin firmar) nunca se utilizan.
Si nunca pones un valor en un personaje que no representa un personaje, y nunca pones un valor en un personaje sin firma que no representa un personaje, ¡todo estará bien!
extra: incluso si utiliza UTF-8 u otras codificaciones (para caracteres especiales) en sus cadenas con C / C ++, todo con este tipo de conversiones estaría bien, por ejemplo, utilizando la codificación UTF-8 (ref. http://lwp.interglacial.com/appf_01.htm ):
char hearts[] = {0xe2, 0x99, 0xa5, 0x00};
char diamonds[] = {0xe2, 0x99, 0xa6, 0x00};
char clubs[] = {0xe2, 0x99, 0xa3, 0x00};
char spades[] = {0xe2, 0x99, 0xa0, 0x00};
printf("hearts (%s)/ndiamonds (%s)/nclubs (%s)/nspades (%s)/n/n", hearts, diamonds, clubs, spades);
La salida de ese código será:
corazones (♥)
diamantes (♦)
clubes (♣)
espadas (♠)
incluso si lanzas cada uno de sus caracteres a caracteres sin firmar.
asi que:
"¿puedo pasar siempre con seguridad un carácter sin firmar * a esta función?" ¡sí!
"¿está garantizado que puedo convertir (cast) de forma segura entre char y unsigned char a voluntad, sin ninguna pérdida de información?" ¡sí!
"¿Puedo convertir (cast) de forma segura entre punteros a caracteres char y unsigned char a voluntad, sin pérdida de información?" ¡sí!
"¿Es la respuesta igual en C y C ++?" ¡sí!
Semánticamente, el paso entre unsigned char *
y char *
es seguro, y aunque se realiza entre ellos, así como en c ++.
Sin embargo, considere el siguiente código de ejemplo:
#include "stdio.h"
void process_unsigned(unsigned char *data_in, int data_len) {
int i=data_len;
unsigned short product=1;
for(; i--; product*=data_in[i])
;
for(i=sizeof(product); i--; ) {
data_in[i]=((unsigned char *)&product)[i];
printf("%d/r/n", data_in[i]);
}
}
void process(char *data_in, int data_len) {
int i=data_len;
unsigned short product=1;
for(; i--; product*=data_in[i])
;
for(i=sizeof(product); i--; ) {
data_in[i]=((unsigned char *)&product)[i];
printf("%d/r/n", data_in[i]);
}
}
void main() {
unsigned char
a[]={1, -1},
b[]={1, -1};
process_unsigned(a, sizeof(a));
process(b, sizeof(b));
getch();
}
salida:
0 255 -1 -1
Todo el código dentro de process_unsigned
y process
es simplemente IDENTICAL . La única diferencia es sin firmar y firmada. Este ejemplo muestra que el código en el cuadro negro , se ve afectado por el SIGNO , y no se garantiza nada entre la persona que llama y la persona que llama.
Por lo tanto, diría que es aplicable solo a pasar , pero ninguna de las otras posibilidades está garantizada.
unsigned char
signed char
o signed char
es solo una interpretación: no se está produciendo una conversión.
Ya que está procesando bytes, para mostrar la intención, sería mejor declarar como
void process(unsigned char *data_in, int data_len);
[Como lo señaló un editor: un char
simple puede ser de tipo firmado o sin signo. Los estándares C y C ++ permiten explícitamente cualquiera de los dos (siempre es un tipo separado de caracteres unsigned char
o caracteres con signed char
, pero tiene el mismo rango que uno de ellos)]