Por que os literais de caracteres C são ints em vez de chars?

103

Em C ++ sizeof('a') == sizeof(char) == 1,. Isso faz sentido intuitivo, uma vez que 'a'é um caractere literal e sizeof(char) == 1conforme definido pelo padrão.

No entanto, em C sizeof('a') == sizeof(int),. Ou seja, parece que os literais de caracteres C são, na verdade, inteiros. Alguem sabe por quê? Posso encontrar muitas menções a essa peculiaridade de C, mas nenhuma explicação de por que ela existe.

Joseph Garvin
fonte
sizeof apenas retornaria o tamanho de um byte, não é? Um char e um int não são iguais em tamanho?
Josh Smeaton
1
Isso provavelmente depende do compilador (e da arquitetura). Importa-se de dizer o que está usando? O padrão (pelo menos até '89) era muito vago.
dmckee --- ex-moderador gatinho
2
não. um char tem sempre 1 byte de tamanho, então sizeof ('a') == 1 sempre (em c ++), enquanto um int pode teoricamente ter tamanho de 1, mas isso exigiria um byte com pelo menos 16 bits, o que é muito improvável: ) então sizeof ('a')! = sizeof (int) é muito provável em C ++ na maioria das implementações
Johannes Schaub - litb
2
... embora esteja sempre errado em C.
Johannes Schaub - litb
22
'a' é um int no ponto C. C chegou primeiro - C fez as regras. C ++ mudou as regras. Você pode argumentar que as regras C ++ fazem mais sentido, mas mudar as regras C faria mais mal do que bem, então o comitê padrão C sabiamente não tocou nisso.
Jonathan Leffler

Respostas:

36

discussão sobre o mesmo assunto

"Mais especificamente, as promoções integrais. Em K&R C era virtualmente (?) Impossível usar um valor de caractere sem que ele fosse promovido para int primeiro, portanto, fazer o caractere constante int em primeiro lugar eliminou essa etapa. Havia e ainda há vários caracteres constantes como 'abcd' ou quantas forem cabíveis em um int. "

Malx
fonte
Constantes de vários caracteres não são portáveis, mesmo entre compiladores em uma única máquina (embora o GCC pareça ser autoconsistente entre plataformas). Consulte: stackoverflow.com/questions/328215
Jonathan Leffler
8
Eu observaria que a) Esta citação não é atribuída; a citação apenas diz "Você discordaria dessa opinião, que foi postada em um tópico anterior discutindo o problema em questão?" ... eb) É ridículo , porque uma charvariável não é um int, portanto, tornar um caractere constante é um caso especial. E é fácil de usar um valor personagem sem promovê-lo: c1 = c2;. OTOH, c1 = 'x'é uma conversão para baixo. Mais importante ainda, sizeof(char) != sizeof('x')que é uma falha grave de linguagem. Quanto às constantes de caracteres multibyte: elas são a razão, mas são obsoletas.
Jim Balter,
27

A pergunta original é "por quê?"

A razão é que a definição de um caractere literal evoluiu e mudou, enquanto tentava permanecer compatível com as versões anteriores do código existente.

Nos dias sombrios do início de C, não havia nenhum tipo. Quando aprendi a programar em C, os tipos foram introduzidos, mas as funções não tinham protótipos para dizer ao chamador quais eram os tipos de argumento. Em vez disso, foi padronizado que tudo o que fosse passado como parâmetro teria o tamanho de um int (incluindo todos os ponteiros) ou seria um duplo.

Isso significa que, quando você está escrevendo a função, todos os parâmetros que não são duplos são armazenados na pilha como ints, não importa como você os declara, e o compilador coloca o código na função para lidar com isso para você.

Isso tornou as coisas um tanto inconsistentes, então quando K&R escreveu seu famoso livro, eles colocaram a regra de que um literal de caractere sempre seria promovido a um int em qualquer expressão, não apenas um parâmetro de função.

Quando o comitê ANSI padronizou C pela primeira vez, eles mudaram essa regra para que um literal de caractere fosse simplesmente um int, já que essa parecia uma maneira mais simples de conseguir a mesma coisa.

Quando o C ++ estava sendo projetado, todas as funções deveriam ter protótipos completos (isso ainda não é exigido em C, embora seja universalmente aceito como boa prática). Por causa disso, foi decidido que um literal de caractere poderia ser armazenado em um char. A vantagem disso em C ++ é que uma função com um parâmetro char e uma função com um parâmetro int têm assinaturas diferentes. Esta vantagem não é o caso em C.

É por isso que eles são diferentes. Evolução...

John Vincent
fonte
2
+1 de mim por realmente responder 'por quê?'. Mas eu discordo da última afirmação - "A vantagem disso em C ++ é que uma função com um parâmetro char e uma função com um parâmetro int têm assinaturas diferentes" - em C ++ ainda é possível que 2 funções tenham parâmetros de mesmo tamanho e assinaturas diferentes, por exemplo, void f(unsigned char)vs void f(signed char).
Peter K
3
@PeterK John poderia ter colocado melhor, mas o que ele diz é essencialmente correto. A motivação para a mudança em C ++ foi, se você escrever f('a'), provavelmente deseja que a resolução de sobrecarga escolha f(char)para aquela chamada em vez de f(int). Os tamanhos relativos de intechar não são relevantes, como você diz.
zwol
21

Não sei as razões específicas pelas quais um literal de caractere em C é do tipo int. Mas em C ++, há um bom motivo para não seguir esse caminho. Considere isto:

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

print('a');

Você esperaria que a chamada para imprimir selecione a segunda versão tomando um caractere. Ter um caractere literal sendo um int tornaria isso impossível. Observe que em C ++ literais com mais de um caractere ainda têm o tipo int, embora seu valor seja definido pela implementação. Então, 'ab'tem tipo int, enquanto 'a'tem tipo char.

Johannes Schaub - litb
fonte
Sim, "Design e evolução do C ++" diz que rotinas sobrecarregadas de entrada / saída foram o principal motivo pelo qual o C ++ mudou as regras.
Max Lybbert
5
Max, sim, eu trapaceei. eu olhei no padrão na seção de compatibilidade :)
Johannes Schaub - litb
18

usando gcc no meu MacBook, tento:

#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 quando executado dá:

'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

o que sugere que um caractere tem 8 bits, como você suspeita, mas um literal de caractere é um int.

dmckee --- gatinho ex-moderador
fonte
7
1 por ser interessante. Muitas vezes as pessoas pensam que sizeof ("a") e sizeof ("") são char *'s e deveriam dar 4 (ou 8). Mas na verdade eles são char [] naquele ponto (sizeof (char [11]) dá 11). Uma armadilha para novatos.
paxdiablo
3
Um literal de caractere não é promovido a um int, ele já é um int. Não há nenhuma promoção acontecendo se o objeto for um operando do operador sizeof. Se houvesse, isso anularia o propósito de sizeof.
Chris Young
@Chris Young: Ya. Verifica. Obrigado.
dmckee --- ex-moderador gatinho
8

Na época em que C estava sendo escrito, a linguagem assembly MACRO-11 do PDP-11 tinha:

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

Esse tipo de coisa é bastante comum na linguagem assembly - os 8 bits baixos manterão o código do caractere, outros bits zerados para 0. O PDP-11 até tinha:

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

Isso forneceu uma maneira conveniente de carregar dois caracteres nos bytes inferior e superior do registro de 16 bits. Você pode então escrevê-los em outro lugar, atualizando alguns dados textuais ou memória da tela.

Então, a ideia de personagens sendo promovidos para registrar tamanho é bastante normal e desejável. Mas, digamos que você precise obter 'A' em um registro não como parte do código de operação embutido em código, mas de algum lugar na memória principal contendo:

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

Se você quiser ler apenas um 'A' desta memória principal em um registro, qual você lerá?

  • Algumas CPUs podem suportar apenas a leitura direta de um valor de 16 bits em um registrador de 16 bits, o que significaria que uma leitura em 20 ou 22 exigiria que os bits de 'X' fossem apagados, dependendo do endianness de um ou outro CPU precisaria mudar para o byte de ordem inferior.

  • Algumas CPUs podem exigir uma leitura alinhada à memória, o que significa que o endereço mais baixo envolvido deve ser um múltiplo do tamanho dos dados: você pode conseguir ler nos endereços 24 e 25, mas não 27 e 28.

Portanto, um compilador que gere código para obter um 'A' no registro pode preferir desperdiçar um pouco de memória extra e codificar o valor como 0 'A' ou 'A' 0 - dependendo do endianness, e também garantindo que ele esteja alinhado corretamente ( ou seja, não em um endereço de memória ímpar).

Meu palpite é que o C simplesmente carregou esse nível de comportamento centrado na CPU, pensando em constantes de caractere ocupando tamanhos de registro de memória, sustentando a avaliação comum de C como um "montador de alto nível".

(Consulte 6.3.3 na página 6-25 de http://www.dmv.net/dec/pdf/macro.pdf )

Tony Delroy
fonte
5

Lembro-me de ler K&R e ver um trecho de código que leria um caractere por vez até atingir EOF. Uma vez que todos os caracteres são válidos para um fluxo de arquivo / entrada, isso significa que EOF não pode ser qualquer valor de char. O que o código fez foi colocar o caractere lido em um int, testar o EOF e, se não for, converter em char.

Sei que isso não responde exatamente à sua pergunta, mas faria algum sentido que o restante dos literais de caracteres fosse sizeof (int) se o literal EOF fosse.

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
fonte
Não acho que 0 seja um caractere válido.
gbjbaanb
3
@gbjbaanb: Claro que é. É o personagem nulo. Pense nisso. Você acha que um arquivo não deve conter bytes zero?
P Daddy
1
Leia a wikipedia - "O valor real de EOF é um número negativo dependente do sistema, comumente -1, que é garantido ser diferente de qualquer código de caractere válido."
Malx
2
Como diz Malx - EOF não é um tipo char - é um tipo int. getchar () e amigos retornam um int, que pode conter qualquer char e também EOF sem conflito. Isso realmente não exigiria que caracteres literais tivessem o tipo int.
Michael Burr
2
EOF == -1 veio muito depois das constantes de caractere de C, então esta não é uma resposta e nem mesmo relevante.
Jim Balter
5

Não vi uma justificativa para isso (literais C char sendo tipos int), mas aqui está algo que Stroustrup tinha a dizer sobre isso (de Design and Evolution 11.2.1 - Resolução de granulação fina):

Em C, o tipo de um literal de caractere, como 'a'é int. Surpreendentemente, fornecer o 'a'tipo charem C ++ não causa problemas de compatibilidade. Exceto pelo exemplo patológico sizeof('a'), cada construção que pode ser expressa em C e C ++ dá o mesmo resultado.

Portanto, na maior parte, não deve causar problemas.

Michael Burr
fonte
Interessante! Meio que contradiz o que outros estavam dizendo sobre como o comitê de padrões C "sabiamente" decidiu não remover essa peculiaridade de C.
j_random_hacker
2

A razão histórica para isso é que C, e seu predecessor B, foram originalmente desenvolvidos em vários modelos de minicomputadores DEC PDP com vários tamanhos de palavras, que suportavam ASCII de 8 bits, mas só podiam realizar cálculos aritméticos em registradores. (Não o PDP-11, no entanto; ele veio depois.) As primeiras versões de C definiam into tamanho da palavra nativa da máquina e qualquer valor menor que um intprecisava ser ampliado intpara ser passado de ou para uma função , ou usado em uma expressão bit a bit, lógica ou aritmética, porque era assim que o hardware subjacente funcionava.

É também por isso que as regras de promoção de inteiros ainda dizem que qualquer tipo de dados menor que um inté promovido para int. As implementações de C também podem usar a matemática do complemento de um em vez do complemento de dois por razões históricas semelhantes. A razão pela qual escapes de caracteres octais e constantes octais são cidadãos de primeira classe em comparação com hexadecimal é que os primeiros minicomputadores DEC tinham tamanhos de palavras divisíveis em pedaços de três bytes, mas não nibbles de quatro bytes.

Davislor
fonte
... e chartinha exatamente 3 dígitos octais
Antti Haapala
1

Este é o comportamento correto, denominado "promoção integral". Isso pode acontecer em outros casos também (principalmente em operadores binários, se bem me lembro).

EDIT: Só para ter certeza, eu verifiquei minha cópia de Expert C Programming: Deep Secrets e confirmei que um literal de char não começa com um tipo int . É inicialmente do tipo char, mas quando é usado em uma expressão , é promovido a int . O seguinte é citado do livro:

Literais de caracteres têm tipo int e eles chegam lá seguindo as regras para promoção do tipo char. Isso é abordado muito brevemente em K&R 1, na página 39, onde diz:

Cada char em uma expressão é convertido em um int .... Observe que todos os floats em uma expressão são convertidos em double .... Visto que um argumento de função é uma expressão, as conversões de tipo também ocorrem quando os argumentos são passados ​​para as funções: in particular, char e short tornam-se int, float torna-se double.

PolyThinker
fonte
Se os outros comentários forem confiáveis, a expressão 'a' começa com o tipo int - nenhuma promoção de tipo é realizada dentro de um sizeof (). Que 'a' tem tipo int é apenas uma peculiaridade de C, ao que parece.
j_random_hacker
2
Um literal de char não tem tipo int. O padrão ANSI / ISO 99 as chama de 'constantes de caracteres inteiros' (para diferenciá-las de 'constantes de caracteres amplas', que têm o tipo wchar_t) e diz especificamente, "Uma constante de caractere inteiro tem o tipo int."
Michael Burr
O que eu quis dizer é que ele não começa com o tipo int, mas sim é convertido em um int de char (resposta editada). Claro, isso provavelmente não diz respeito a ninguém, exceto aos escritores do compilador, pois a conversão é sempre feita.
PolyThinker
3
Não! Se você ler o padrão ANSI / ISO 99 C , descobrirá que em C, a expressão 'a' começa com o tipo int. Se você tem uma função f void (int) e uma variável char c, então f (c) vai realizar a promoção integral, mas f ( 'a') não será como o tipo de 'a' é int. Estranho mas verdade.
j_random_hacker
2
"Apenas para ter certeza" - você pode ter mais certeza lendo a declaração: "Literais de caracteres têm tipo int". "Só posso supor que foi uma das mudanças silenciosas" - você presume erroneamente. Literais de caracteres em C sempre foram do tipo int.
Jim Balter,
0

Não sei, mas suponho que foi mais fácil implementar dessa forma e realmente não importou. Somente em C ++, quando o tipo pôde determinar qual função seria chamada, ela precisou ser corrigida.

Roland Rabien
fonte
0

Eu realmente não sabia disso. Antes da existência dos protótipos, qualquer coisa mais estreita do que um int era convertido em um int ao ser usado como um argumento de função. Isso pode ser parte da explicação.

Blaisorblade
fonte
1
Outra pobre "resposta". A conversão automática de charpara inttornaria totalmente desnecessário que as constantes de caracteres fossem ints. O que é relevante é que a linguagem trata as constantes de caractere de maneira diferente (dando-lhes um tipo diferente) das charvariáveis, e o que é necessário é uma explicação dessa diferença.
Jim Balter,
Obrigado pela explicação que você deu abaixo. Você pode querer descrever sua explicação mais detalhadamente em uma resposta, onde ela pertence, pode ser votada e facilmente vista pelos visitantes. Além disso, nunca disse que tinha uma boa resposta aqui. Portanto, seu julgamento de valor não ajuda em nada.
Blaisorblade
0

Isso é apenas tangencial às especificações do idioma, mas em hardware a CPU geralmente tem apenas um tamanho de registro - 32 bits, digamos - e sempre que ele realmente funciona em um caractere (adicionando, subtraindo ou comparando) há uma conversão implícita para int quando é carregado no registrador. O compilador cuida de mascarar e mudar o número adequadamente após cada operação, de modo que se você adicionar, digamos, 2 a (unsigned char) 254, ele se transformará em 0 em vez de 256, mas dentro do silício é realmente um int até salvá-lo de volta na memória.

É meio que um ponto acadêmico porque a linguagem poderia ter especificado um tipo literal de 8 bits de qualquer maneira, mas neste caso a especificação da linguagem reflete mais de perto o que a CPU está realmente fazendo.

(x86 experientes podem notar que há, por exemplo, um addh op nativo que adiciona os registradores curtos em uma única etapa, mas dentro do núcleo RISC isso se traduz em duas etapas: adicione os números, em seguida, estenda o sinal, como um par add / extsh em o PowerPC)

Crashworks
fonte
1
Mais uma resposta errada. A questão aqui é por que os literais de caracteres e charvariáveis ​​têm tipos diferentes. As promoções automáticas, que refletem o hardware, não são relevantes - elas são na verdade anti-relevantes, porque as charvariáveis ​​são promovidas automaticamente, então não há razão para os literais de caracteres não serem do tipo char. O verdadeiro motivo são os literais multibyte, que agora estão obsoletos.
Jim Balter,
Os literais multibyte @Jim Balter não são obsoletos; há caracteres Unicode e UTF multibyte.
Crashworks de
@Crashworks Estamos falando sobre literais de caracteres multibyte , não literais de string multibyte . Tente prestar atenção.
Jim Balter,
4
Chrashworks escreveu personagens . Você deveria ter escrito que literais de caracteres largos (digamos L'à ') levam mais bytes, mas não são chamados de literais de caracteres multibyte. Ser menos arrogante o ajudaria a ser mais preciso.
Blaisorblade
@Blaisorblade Literais de caracteres largos não são relevantes aqui - eles não têm nada a ver com o que escrevi. Fui preciso e falta-lhe compreensão, e sua tentativa falsa de me corrigir é o que é arrogante.
Jim Balter,