¿Por qué los literales de caracteres C son ints en lugar de chars?

103

En C ++, sizeof('a') == sizeof(char) == 1. Esto tiene sentido intuitivo, ya que 'a'es un carácter literal y sizeof(char) == 1como 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.

Joseph Garvin
fuente
sizeof solo devolvería el tamaño de un byte, ¿no? ¿No son un char y un int del mismo tamaño?
Josh Smeaton
1
Probablemente esto dependa del compilador (y de la arquitectura). ¿Te importaría decir lo que estás usando? El estándar (al menos hasta el '89) era muy flexible.
dmckee --- ex-moderador gatito
2
No. un char es siempre 1 byte grande, por lo que sizeof ('a') == 1 siempre (en c ++), mientras que un int puede ser teóricamente sizeof de 1, pero eso requeriría un byte que tenga al menos 16 bits, lo cual es muy poco probable: ) así que sizeof ('a')! = sizeof (int) es muy probable en C ++ en la mayoría de las implementaciones
Johannes Schaub - litb
2
... aunque siempre está mal en C.
Johannes Schaub - litb
22
'a' es un int en C - período. C llegó primero, C hizo las reglas. C ++ cambió las reglas. Se puede argumentar que las reglas de C ++ tienen más sentido, pero cambiar las reglas de C haría más daño que bien, por lo que el comité estándar de C sabiamente no ha tocado esto.
Jonathan Leffler

Respuestas:

36

discusión sobre el mismo tema

"Más específicamente las promociones integrales. En K&R C era virtualmente (?) Imposible usar un valor de carácter sin ser promovido a int primero, por lo que hacer que el carácter sea constante en primer lugar eliminó ese paso. Había y todavía hay múltiples caracteres constantes como 'abcd' o las que quepan en un int. "

Malx
fuente
Las constantes de varios caracteres no son portátiles, incluso entre compiladores en una sola máquina (aunque GCC parece ser autoconsistente en todas las plataformas). Ver: stackoverflow.com/questions/328215
Jonathan Leffler
8
Me gustaría señalar que a) esta cita no se atribuye; la cita simplemente dice "¿No está de acuerdo con esta opinión, que se publicó en un hilo anterior sobre el tema en cuestión?" ... yb) Es ridículo , porque una charvariable 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.
Jim Balter
27

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...

John Vincent
fuente
2
+1 de mí por responder realmente '¿por qué?'. Pero no estoy de acuerdo con la última afirmación - "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" - en C ++ todavía es posible que 2 funciones tengan parámetros de mismo tamaño y diferentes firmas, por ejemplo void f(unsigned char)Vs void f(signed char).
Peter K
3
@PeterK John podría haberlo dicho mejor, pero lo que dice es esencialmente exacto. La motivación para el cambio en C ++ fue, si escribe f('a'), probablemente desee elegir la resolución de sobrecarga f(char)para esa llamada en lugar de f(int). Los tamaños relativos de inty charno son relevantes, como dices.
zwol
21

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:

void print(int);
void print(char);

print('a');

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 tipo int, mientras que 'a'tiene tipo char.

Johannes Schaub - litb
fuente
Sí, "Diseño y evolución de C ++" dice que las rutinas de entrada / salida sobrecargadas fueron la razón principal por la que C ++ cambió las reglas.
Max Lybbert
5
Max, sí, hice trampa. miré en el estándar en la sección de compatibilidad :)
Johannes Schaub - litb
18

usando gcc en mi MacBook, intento:

#include <stdio.h>
#define test(A) do{printf(#A":\t%i\n",sizeof(A));}while(0)
int main(void){
  test('a');
  test("a");
  test("");
  test(char);
  test(short);
  test(int);
  test(long);
  test((char)0x0);
  test((short)0x0);
  test((int)0x0);
  test((long)0x0);
  return 0;
};

que cuando se ejecuta da:

'a':    4
"a":    2
"":     1
char:   1
short:  2
int:    4
long:   4
(char)0x0:      1
(short)0x0:     2
(int)0x0:       4
(long)0x0:      4

lo que sugiere que un carácter tiene 8 bits, como sospecha, pero un carácter literal es un int.

dmckee --- ex-gatito moderador
fuente
7
+1 por ser interesante. La gente suele pensar que sizeof ("a") y sizeof ("") son caracteres * y deberían dar 4 (u 8). Pero de hecho son char [] en ese punto (sizeof (char [11]) da 11). Una trampa para novatos.
paxdiablo
3
Un carácter literal no se promueve a un int, ya es un int. No hay promoción en absoluto si el objeto es un operando del tamaño del operador. Si lo hubiera, esto frustraría el propósito de sizeof.
Chris Young
@Chris Young: Sí. Cheque. Gracias.
dmckee --- ex-moderador gatito
8

Cuando se estaba escribiendo C, el lenguaje ensamblador MACRO-11 del PDP-11 tenía:

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

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:

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

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:

address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'

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 )

Tony Delroy
fuente
5

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.

int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;

while ((r = getc(file)) != EOF)
{
  *(p++) = (char) r;
}
Kyle Cronin
fuente
Sin embargo, no creo que 0 sea un carácter válido.
gbjbaanb
3
@gbjbaanb: Claro que lo es. Es el carácter nulo. Piénsalo. ¿Crees que no se debería permitir que un archivo contenga cero bytes?
P Daddy
1
Leer wikipedia - "El valor real de EOF es un número negativo dependiente del sistema, comúnmente -1, que se garantiza que no es igual a cualquier código de carácter válido".
Malx
2
Como dice Malx, EOF no es un tipo char, es un tipo int. getchar () y sus amigos devuelven un int, que puede contener cualquier char y EOF sin conflicto. Esto realmente no requeriría que los caracteres literales tuvieran el tipo int.
Michael Burr
2
EOF == -1 vino mucho después de las constantes de caracteres de C, por lo que esta no es una respuesta y ni siquiera es relevante.
Jim Balter
5

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):

En C, el tipo de un carácter literal como 'a'es int. Sorprendentemente, dar 'a'tipo charen C ++ no causa ningún problema de compatibilidad. Excepto por el ejemplo patológico sizeof('a'), todas las construcciones que se pueden expresar tanto en C como en C ++ dan el mismo resultado.

Entonces, en su mayor parte, no debería causar problemas.

Michael Burr
fuente
¡Interesante! Un poco contradice lo que otros decían sobre cómo el comité de estándares de C decidió "sabiamente" no eliminar esta peculiaridad de C.
j_random_hacker
2

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 intcomo el tamaño de la palabra nativa de la máquina, y cualquier valor menor que an intdebía ampliarse para intpasar 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 intse promueve cualquier tipo de datos menor que an int. 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.

Davislor
fuente
... y chartenía exactamente 3 dígitos octales de largo
Antti Haapala
1

Este 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:

Los literales de caracteres tienen el tipo int y llegan allí siguiendo las reglas de promoción del tipo char. Esto se trata muy brevemente en K&R 1, en la página 39, donde dice:

Cada carácter de una expresión se convierte en un int .... Observe que todos los flotantes en una expresión se convierten en doble .... Dado que un argumento de función es una expresión, las conversiones de tipos también tienen lugar cuando se pasan argumentos a funciones: en particular, char y short se convierten en int, float se vuelve doble.

PolyThinker
fuente
Si hay que creer en los otros comentarios, la expresión 'a' comienza con el tipo int; no se realiza ninguna promoción de tipo dentro de un tamaño de (). Parece que 'a' tiene el tipo int es solo una peculiaridad de C.
j_random_hacker
2
Un literal Char hace tener tipo int. El estándar ANSI / ISO 99 las llama 'constantes de caracteres enteros' (para diferenciarlas de las 'constantes de caracteres anchos', que tienen el tipo wchar_t) y específicamente dice, "Una constante de caracteres enteros tiene el tipo int".
Michael Burr
Lo que quise decir es que no comienza con el tipo int, sino que se convierte en un int de char (respuesta editada). Por supuesto, esto probablemente no concierne a nadie excepto a los escritores de compiladores, ya que la conversión siempre se realiza.
PolyThinker
3
¡No! Si lee el estándar ANSI / ISO 99 C , encontrará que en C, la expresión 'a' comienza con el tipo int. Si usted tiene una función void f (int) y una variable de tipo char c, entonces f (c) se realice promoción integral, pero f ( 'a') será no como el tipo de 'a' es ya int. Extraño pero cierto.
j_random_hacker
2
"Solo para estar seguro": puede estar más seguro si lee la declaración: "Los literales de caracteres tienen el tipo int". "Sólo puedo asumir que fue uno de los cambios silenciosos", asumes erróneamente. Los literales de caracteres en C siempre han sido de tipo int.
Jim Balter
0

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.

Roland Rabien
fuente
0

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.

Blaisorblade
fuente
1
Otra mala "respuesta". La conversión automática de chara intharí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 las charvariables, y lo que se necesita es una explicación de esa diferencia.
Jim Balter
Gracias por la explicación que dio a continuación. Es posible que desee describir su explicación de manera más completa en una respuesta, a dónde pertenece, dónde se puede votar y los visitantes pueden verla fácilmente. Además, nunca dije que tuviera una buena respuesta aquí. Por lo tanto, su juicio de valor no ayuda.
Blaisorblade
0

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)

Crashworks
fuente
1
Sin embargo, otra respuesta incorrecta. El problema aquí es por qué los caracteres literales y charvariables tienen diferentes tipos. Las promociones automáticas, que reflejan el hardware, no son relevantes; en realidad, son anti-relevantes, porque las charvariables se promueven automáticamente, por lo que no hay razón para que los literales de caracteres no sean de tipo char. La verdadera razón son los literales multibyte, que ahora están obsoletos.
Jim Balter
@Jim Balter Los literales multibyte no están obsoletos en absoluto; hay caracteres Unicode y UTF multibyte.
Crashworks
@Crashworks Estamos hablando de literales de caracteres multibyte , no literales de cadenas multibyte . Trate de prestar atención.
Jim Balter
4
Chrashworks sí escribió personajes . Debería haber escrito que los literales de caracteres anchos (digamos L'à ') toman más bytes pero no se denominan literales de caracteres multibyte. Ser menos arrogante te ayudaría a ser más preciso tú mismo.
Blaisorblade
@Blaisorblade Los literales de caracteres anchos no son relevantes aquí, no tienen nada que ver con lo que escribí. Fui preciso y le falta comprensión y su falso intento de corregirme es lo que es arrogante.
Jim Balter