En C ++, sizeof('a') == sizeof(char) == 1
. Esto tiene sentido intuitivo, ya que 'a'
es un carácter literal y sizeof(char) == 1
como lo define el estándar.
En C sin embargo, sizeof('a') == sizeof(int)
. Es decir, parece que los literales de caracteres C son en realidad números enteros. ¿Alguien sabe por qué? Puedo encontrar muchas menciones de esta peculiaridad de C, pero ninguna explicación de por qué existe.
Respuestas:
discusión sobre el mismo tema
fuente
char
variable no es un int, por lo que hacer que un carácter constante sea uno es un caso especial. Y es fácil de usar un valor de carácter sin promover que:c1 = c2;
. OTOH,c1 = 'x'
es una conversión a la baja. Lo más importante,sizeof(char) != sizeof('x')
que es un grave error de lenguaje. En cuanto a las constantes de caracteres multibyte: son la razón, pero están obsoletas.La pregunta original es "¿por qué?"
La razón es que la definición de un carácter literal ha evolucionado y cambiado, al mismo tiempo que se intenta mantener la compatibilidad con el código existente.
En los días oscuros de principios de C, no había tipos en absoluto. Cuando aprendí a programar en C por primera vez, se habían introducido tipos, pero las funciones no tenían prototipos para decirle a la persona que llama cuáles eran los tipos de argumentos. En cambio, se estandarizó que todo lo que se pasara como parámetro sería del tamaño de un int (esto incluía todos los punteros) o sería un doble.
Esto significaba que cuando estaba escribiendo la función, todos los parámetros que no eran dobles se almacenaban en la pila como ints, sin importar cómo los declarara, y el compilador colocó código en la función para manejar esto por usted.
Esto hizo que las cosas fueran algo inconsistentes, por lo que cuando K&R escribió su famoso libro, pusieron la regla de que un carácter literal siempre se promocionaría a un int en cualquier expresión, no solo un parámetro de función.
Cuando el comité ANSI estandarizó por primera vez C, cambiaron esta regla para que un carácter literal fuera simplemente un int, ya que parecía una forma más sencilla de lograr lo mismo.
Cuando se estaba diseñando C ++, se requería que todas las funciones tuvieran prototipos completos (esto todavía no es requerido en C, aunque es universalmente aceptado como una buena práctica). Debido a esto, se decidió que un carácter literal podría almacenarse en un char. La ventaja de esto en C ++ es que una función con un parámetro char y una función con un parámetro int tienen firmas diferentes. Esta ventaja no es el caso de C.
Por eso son diferentes. Evolución...
fuente
void f(unsigned char)
Vsvoid f(signed char)
.f('a')
, probablemente desee elegir la resolución de sobrecargaf(char)
para esa llamada en lugar def(int)
. Los tamaños relativos deint
ychar
no son relevantes, como dices.No sé las razones específicas por las que un carácter literal en C es de tipo int. Pero en C ++, hay una buena razón para no seguir ese camino. Considera esto:
Es de esperar que la llamada a imprimir seleccione la segunda versión con un carácter. Tener un carácter literal como int lo haría imposible. Tenga en cuenta que en C ++ los literales que tienen más de un carácter todavía tienen el tipo int, aunque su valor está definido por la implementación. Entonces,
'ab'
tiene tipoint
, mientras que'a'
tiene tipochar
.fuente
usando gcc en mi MacBook, intento:
que cuando se ejecuta da:
lo que sugiere que un carácter tiene 8 bits, como sospecha, pero un carácter literal es un int.
fuente
Cuando se estaba escribiendo C, el lenguaje ensamblador MACRO-11 del PDP-11 tenía:
Este tipo de cosas es bastante común en el lenguaje ensamblador: los 8 bits bajos mantendrán el código de carácter, otros bits se borran a 0. PDP-11 incluso tenía:
Esto proporcionó una manera conveniente de cargar dos caracteres en los bytes bajos y altos del registro de 16 bits. Luego, puede escribirlos en otro lugar, actualizando algunos datos textuales o memoria de pantalla.
Por lo tanto, la idea de que los personajes se promuevan para registrar el tamaño es bastante normal y deseable. Pero, digamos que necesita introducir 'A' en un registro no como parte del código de operación codificado, sino desde algún lugar de la memoria principal que contenga:
Si desea leer solo una 'A' de esta memoria principal en un registro, ¿cuál leería?
Es posible que algunas CPU solo admitan directamente la lectura de un valor de 16 bits en un registro de 16 bits, lo que significaría que una lectura a 20 o 22 requeriría que los bits de 'X' se borren, y dependiendo de la endianidad de la CPU, uno u otro necesitaría cambiar al byte de orden inferior.
Algunas CPU pueden requerir una lectura alineada con la memoria, lo que significa que la dirección más baja involucrada debe ser un múltiplo del tamaño de los datos: es posible que pueda leer de las direcciones 24 y 25, pero no 27 y 28.
Por lo tanto, un compilador que genera código para obtener una 'A' en el registro puede preferir desperdiciar un poco de memoria adicional y codificar el valor como 0 'A' o 'A' 0, dependiendo de la endianidad, y también asegurándose de que esté alineado correctamente ( es decir, no en una dirección de memoria impar).
Supongo que los C simplemente llevaron este nivel de comportamiento centrado en la CPU, pensando en constantes de caracteres que ocupan tamaños de registro de memoria, lo que confirma la evaluación común de C como un "ensamblador de alto nivel".
(Ver 6.3.3 en la página 6-25 de http://www.dmv.net/dec/pdf/macro.pdf )
fuente
Recuerdo leer K&R y ver un fragmento de código que leería un carácter a la vez hasta que llegara a EOF. Dado que todos los caracteres son caracteres válidos para estar en un archivo / flujo de entrada, esto significa que EOF no puede tener ningún valor de carácter. Lo que hizo el código fue poner el carácter leído en un int, luego probar para EOF, luego convertir a un char si no lo era.
Me doy cuenta de que esto no responde exactamente a su pregunta, pero tendría algún sentido que el resto de los literales de caracteres fueran sizeof (int) si el literal EOF fuera.
fuente
No he visto una justificación para ello (los literales de C char son tipos int), pero aquí hay algo que Stroustrup tuvo que decir al respecto (de Design and Evolution 11.2.1 - Resolución de grano fino):
Entonces, en su mayor parte, no debería causar problemas.
fuente
La razón histórica de esto es que C, y su predecesor B, se desarrollaron originalmente en varios modelos de miniordenadores DEC PDP con varios tamaños de palabras, que admitían ASCII de 8 bits pero solo podían realizar operaciones aritméticas en registros. (No el PDP-11, sin embargo, que vino más tarde). Las primeras versiones de C se definían
int
como el tamaño de la palabra nativa de la máquina, y cualquier valor menor que anint
debía ampliarse paraint
pasar a una función o desde ella. , o usado en una expresión lógica o aritmética bit a bit, porque así era como funcionaba el hardware subyacente.Ésa es también la razón por la que las reglas de promoción de enteros todavía dicen que
int
se promueve cualquier tipo de datos menor que anint
. Las implementaciones de C también pueden usar matemáticas de complemento a uno en lugar de complemento a dos por razones históricas similares. La razón por la que los caracteres octales escapan y las constantes octales son ciudadanos de primera clase en comparación con hexadecimal es igualmente porque esas primeras miniordenadores DEC tenían tamaños de palabras divisibles en trozos de tres bytes pero no nibbles de cuatro bytes.fuente
char
tenía exactamente 3 dígitos octales de largoEste es el comportamiento correcto, llamado "promoción integral". También puede suceder en otros casos (principalmente operadores binarios, si mal no recuerdo).
EDITAR: Solo para estar seguro, verifiqué mi copia de Expert C Programming: Deep Secrets , y confirmé que un literal char no comienza con un tipo int . Inicialmente es de tipo char, pero cuando se usa en una expresión , se promueve a un int . Se cita lo siguiente del libro:
fuente
No lo sé, pero supongo que fue más fácil implementarlo de esa manera y realmente no importaba. No fue hasta C ++ cuando el tipo pudo determinar qué función se llamaría que necesitaba ser reparada.
fuente
De hecho, no lo sabía. Antes de que existieran los prototipos, cualquier cosa más estrecha que un int se convertía en un int cuando se usaba como un argumento de función. Eso puede ser parte de la explicación.
fuente
char
aint
haría bastante innecesario que las constantes de caracteres sean enteras. Lo relevante es que el lenguaje trata las constantes de caracteres de manera diferente (dándoles un tipo diferente) de laschar
variables, y lo que se necesita es una explicación de esa diferencia.Esto es solo tangencial a la especificación del idioma, pero en el hardware, la CPU generalmente solo tiene un tamaño de registro (32 bits, digamos) y, por lo tanto, siempre que realmente funciona en un carácter (al agregarlo, restarlo o compararlo) hay una conversión implícita a int cuando se carga en el registro. El compilador se encarga de enmascarar y cambiar correctamente el número después de cada operación, de modo que si agrega, digamos, 2 a (carácter sin firmar) 254, se ajustará a 0 en lugar de 256, pero dentro del silicio es realmente un int hasta que lo guarde en la memoria.
Es una especie de punto académico porque el lenguaje podría haber especificado un tipo literal de 8 bits de todos modos, pero en este caso la especificación del lenguaje refleja más de cerca lo que realmente está haciendo la CPU.
(Los expertos de x86 pueden notar que, por ejemplo, hay un complemento nativo que agrega los registros de ancho corto en un paso, pero dentro del núcleo RISC esto se traduce en dos pasos: sumar los números, luego extender el signo, como un par agregar / extsh en el PowerPC)
fuente
char
variables tienen diferentes tipos. Las promociones automáticas, que reflejan el hardware, no son relevantes; en realidad, son anti-relevantes, porque laschar
variables se promueven automáticamente, por lo que no hay razón para que los literales de caracteres no sean de tipochar
. La verdadera razón son los literales multibyte, que ahora están obsoletos.