Este código-fonte está ativando uma string em C. Como ele faz isso?

106

Estou lendo um código de emulador e rebati algo realmente estranho:

switch (reg){
    case 'eax':
    /* and so on*/
}

Como isso é possível? Achei que você pudesse apenas switchnos tipos integrais. Existe algum truque macro acontecendo?

Ian Colton
fonte
29
não é a string 'eax'e enumera o valor inteiro constante
P__J__ de
12
Aspas simples, não duplas. Uma constante de caractere é promovida a int, então é legal. No entanto, o valor de uma constante de vários caracteres é definido pela implementação, portanto, o código pode não funcionar como esperado em outro compilador. Por exemplo, eaxpode ser 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, ou qualquer outra coisa.
Davislor
2
@Davislor: dado o nome da variável "reg" e o fato de que eax é um registrador x86, acho que o comportamento definido pela implementação deveria ser OK, porque é o mesmo em todos os lugares em que é usado no código. Desde que 'eax' != 'ebx', é claro, falhe apenas um ou dois de seus exemplos. Embora possa haver algum código em algum lugar que, de fato *(int*)("eax") == 'eax', assume e, portanto, falha na maioria dos seus exemplos.
Steve Jessop
2
@SteveJessop Não discordo do que você diz, mas existe o perigo real de que alguém tente compilar o código em um compilador diferente, mesmo para a mesma arquitetura, e obtenha um comportamento diferente. Por exemplo, 'eax'pode comparar igual a 'ebx'ou a 'ax', e a instrução switch não funcionaria conforme o esperado.
Davislor
1
Todo esse mistério teria sido rapidamente dissipado se você tivesse olhado / mostrado o tipo de dados de reg.
ths

Respostas:

146

(Só você pode responder à parte dos "truques de macro" - a menos que cole mais código. Mas não há muito aqui para as macros trabalharem - formalmente, você não tem permissão para redefinir palavras-chave ; o comportamento ao fazer isso é indefinido.)

Para conseguir a legibilidade do programa, o desenvolvedor inteligente está explorando o comportamento definido pela implementação . não'eax' é uma string, mas uma constante de vários caracteres . Observe com muito cuidado os caracteres de aspas simples ao redor . Provavelmente, está dando a você um, no seu caso, que é exclusivo para essa combinação de personagens. (Freqüentemente, cada caractere ocupa 8 bits em um 32 bits ). E todo mundo sabe que você pode em umeaxintintswitchint !

Finalmente, uma referência padrão:

O padrão C99 diz:

6.4.4.4p10: "O valor de uma constante de caractere inteiro contendo mais de um caractere (por exemplo, 'ab'), ou contendo um caractere ou sequência de escape que não mapeia para um caractere de execução de byte único, é definido pela implementação. "

Bate-Seba
fonte
55
Apenas no caso de alguém ver isso e entrar em pânico, "definido pela implementação" é necessário para funcionar e ser documentado por seu compilador de alguma forma apropriada (o padrão não exige que o comportamento seja intuitivo ou que a documentação seja boa, mas ...). Isso é "seguro" para um codificador que entende completamente o que está escrevendo, ao contrário de "indefinido".
Leushenko
7
@Justin Embora pudesse, seria bastante perverso. Se ele não fizer o que a resposta sugere ser mais provável, a próxima possibilidade é que ele apenas use o primeiro caractere e ignore o resto.
Barmar
5
@ZanLynx Não tenho certeza, mas acredito que o recurso é muito anterior ao Unicode e outros padrões MBCS. "Números mágicos" que parecem texto em despejos de memória e IDs de fragmentos de formato de arquivo no estilo RIFF foram os primeiros aplicativos que conheci.
Russell Borogove
16
@ jpmc26 Este não é um comportamento indefinido, é definido pela implementação. Portanto, a menos que a documentação do compilador mencione demônios, seu nariz está seguro.
Barmar
7
@ZanLynx: Temo que a intenção original seja anterior a Unicode, UTF-8 e qualquer codificação de caractere multibyte em quase 20 anos. constantes de vários caracteres eram apenas uma maneira prática de expressar inteiros representando grupos de 2, 3 ou 4 bytes (dependendo dos tamanhos de bytes e inteiros). Inconsistências nas implementações e arquiteturas levou o comitê a declarar isso como implementação definida , o que significa que não há nenhuma maneira portátil para calcular o valor de 'ab'partir 'a'e 'b'.
chqrlie
45

De acordo com o padrão C (6.8.4.2 A declaração switch)

3 A expressão de cada rótulo de caso deve ser uma expressão constante inteira ...

e (6.6 Expressões constantes)

6 Uma expressão de constante inteira deve ter tipo inteiro e deve ter apenas operandos que são constantes inteiras, constantes de enumeração, constantes de caractere , tamanho de expressões cujos resultados são constantes inteiras e constantes flutuantes que são os operandos imediatos de casts. Os operadores de conversão em uma expressão de constante inteira devem converter apenas tipos aritméticos em tipos inteiros, exceto como parte de um operando para o operador sizeof.

Agora o que é 'eax' ?

O padrão C (6.4.4.4 constantes de caracteres)

2 Uma constante de caractere inteiro é uma sequência de um ou mais caracteres multibyte entre aspas simples , como em 'x' ...

Então, 'eax'é uma constante de caractere inteiro de acordo com o parágrafo 10 da mesma seção

  1. ... O valor de uma constante de caractere inteiro contendo mais de um caractere (por exemplo, 'ab'), ou contendo um caractere ou sequência de escape que não mapeia para um caractere de execução de byte único, é definido pela implementação.

Portanto, de acordo com a primeira citação mencionada, pode ser um operando de uma expressão de constante inteira que pode ser usada como um rótulo de caso.

Observe que uma constante de caractere (entre aspas simples) tem tipo inte não é o mesmo que uma string literal (uma sequência de caracteres entre aspas duplas) que tem um tipo de matriz de caracteres.

Vlad de Moscou
fonte
12

Como já foi dito, este é um int constante e seu valor real é definido pela implementação.

Presumo que o resto do código se pareça com

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

Você pode ter certeza de que 'eax' na primeira parte tem o mesmo valor que 'eax' na segunda parte, então tudo deu certo, certo? ... errado.

Em um comentário, @Davislor lista alguns valores possíveis para 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, ou qualquer outra coisa

Observe o primeiro valor potencial? Isso mesmo 'e', ignorando os outros dois personagens. O problema é o programa provavelmente usa 'eax', 'ebx'e assim por diante. Se todas essas constantes tiverem o mesmo valor que 'e'você acaba com

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

Isso não parece muito bom, não é?

A parte boa sobre "definido por implementação" é que o programador pode verificar a documentação de seu compilador e ver se ele faz algo sensato com essas constantes. Se isso acontecer, fique em casa gratuitamente.

A parte ruim é que algum outro pobre sujeito pode pegar o código e tentar compilá-lo usando algum outro compilador. Erro de compilação instantânea. O programa não é portátil.

Como @zwol apontou nos comentários, a situação não é tão ruim quanto eu pensava, no caso de o código não compilar. Isso fornecerá pelo menos um nome de arquivo exato e um número de linha para o problema. Mesmo assim, você não terá um programa funcionando.

Stig Hemmer
fonte
1
além de alguma forma de assert('eax' != 'ebx'); //if this fails you can't compile the code because...há algo que o autor original poderia fazer para evitar outras falhas do compilador sem substituir a construção inteiramente>
Dan Is Fiddling Por Firelight
6
Dois rótulos de caso com o mesmo valor são uma violação de restrição (6.8.4.2p3: "... nenhuma das duas expressões constantes de caso na mesma instrução switch deve ter o mesmo valor após a conversão"), portanto, desde que todo o código trata os valores dessas constantes como opacos, é garantido que funcionará ou não será compilado.
zwol
A pior parte é que o pobre coitado compilando em outro compilador provavelmente não verá nenhum erro de tempo de compilação (ligar o INTS está certo); em vez disso, erros de tempo de execução aparecerão ...
tucuxi
1

O fragmento de código usa uma estranheza histórica chamada constante de caractere multi-caracteres , também conhecida como multi-chars .

'eax' é uma constante inteira cujo valor é definido pela implementação.

Aqui está uma página interessante sobre vários caracteres e como eles podem ser usados, mas não devem:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


Olhando mais para trás no espelho retrovisor, aqui está como o manual C original de Dennis Ritchie dos bons velhos tempos ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) especificava constantes de caracteres .

2.3.2 Constantes de caracteres

Uma constante de caractere tem 1 ou 2 caracteres entre aspas simples '' '''. Dentro de uma constante de caractere, uma aspa simples deve ser precedida por uma barra invertida '' \''. Certos caracteres não gráficos e o próprio '' \'' podem ser escapados de acordo com a seguinte tabela:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

O escape '' \ddd'' consiste na barra invertida seguida por 1, 2 ou 3 dígitos octais que são usados ​​para especificar o valor do caractere desejado. Um caso especial dessa construção é '' \0'' (não seguido por um dígito) que indica um caractere nulo.

As constantes de caracteres se comportam exatamente como inteiros (não, em particular, como objetos do tipo de caractere). Em conformidade com a estrutura de endereçamento do PDP-11, uma constante de caractere de comprimento 1 tem o código para o caractere dado no byte de ordem inferior e 0 no byte de ordem superior; uma constante de caractere de comprimento 2 possui o código para o primeiro caractere no byte inferior e o do segundo caractere no byte superior. Constantes de caracteres com mais de um caractere são inerentemente dependentes da máquina e devem ser evitadas.

A última frase é tudo que você precisa lembrar sobre esta construção curiosa: constantes de caractere com mais de um caractere são inerentemente dependentes da máquina e devem ser evitadas.

chqrlie
fonte