Em C ++ sizeof('a') == sizeof(char) == 1
,. Isso faz sentido intuitivo, uma vez que 'a'
é um caractere literal e sizeof(char) == 1
conforme 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.
Respostas:
discussão sobre o mesmo assunto
fonte
char
variá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.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...
fonte
void f(unsigned char)
vsvoid f(signed char)
.f('a')
, provavelmente deseja que a resolução de sobrecarga escolhaf(char)
para aquela chamada em vez def(int)
. Os tamanhos relativos deint
echar
não são relevantes, como você diz.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:
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 tipoint
, enquanto'a'
tem tipochar
.fonte
usando gcc no meu MacBook, tento:
que quando executado dá:
o que sugere que um caractere tem 8 bits, como você suspeita, mas um literal de caractere é um int.
fonte
Na época em que C estava sendo escrito, a linguagem assembly MACRO-11 do PDP-11 tinha:
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:
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:
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 )
fonte
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.
fonte
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):
Portanto, na maior parte, não deve causar problemas.
fonte
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
int
o tamanho da palavra nativa da máquina e qualquer valor menor que umint
precisava ser ampliadoint
para 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 paraint
. 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.fonte
char
tinha exatamente 3 dígitos octaisEste é 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:
fonte
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.
fonte
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.
fonte
char
paraint
tornaria 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) daschar
variáveis, e o que é necessário é uma explicação dessa diferença.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)
fonte
char
variá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 aschar
variáveis são promovidas automaticamente, então não há razão para os literais de caracteres não serem do tipochar
. O verdadeiro motivo são os literais multibyte, que agora estão obsoletos.