O printf (“% x”, 1) invoca um comportamento indefinido?

De acordo com o padrão C (6.5.2.2, parágrafo 6)

Se a expressão que denota a function chamada tiver um tipo que não inclui um protótipo, as promoções de inteiros serão executadas em cada argumento e os argumentos que tiverem o tipo float serão promovidos para o dobro. Essas são chamadas de promoções de argumento padrão. Se o número de argumentos não for igual ao número de parâmetros, o comportamento é indefinido. Se a function é definida com um tipo que inclui um protótipo e o protótipo termina com reticências (, …) ou os tipos de argumentos após a promoção não são compatíveis com os tipos de parâmetros, o comportamento é indefinido. Se a function é definida com um tipo que não inclui um protótipo, e os tipos de argumentos após a promoção não são compatíveis com os dos parâmetros após a promoção, o comportamento é indefinido, exceto nos seguintes casos:

  • um tipo promovido é um tipo inteiro assinado, o outro tipo promovido é o tipo inteiro sem sinal correspondente e o valor é representável em ambos os tipos;
  • ambos os tipos são pointers para versões quali fi cadas ou não qualificadas de um tipo de caractere ou nulo.

Assim, em geral, não há nada de errado em passar um int para uma function variádica que espera um unsigned int (ou vice-versa), desde que o valor transmitido caiba nos dois tipos. No entanto, a especificação para leituras de printf (7.19.6.1, parágrafo 9):

Se uma especificação de conversão for inválida, o comportamento é indefinido. Se algum argumento não for o tipo correto para a especificação de conversão correspondente, o comportamento é indefinido.

Nenhuma exceção é feita para incompatibilidade assinada / não assinada.

Isso significa que printf("%x", 1) invoca um comportamento indefinido?

Eu acredito que é tecnicamente indefinido, porque o “tipo correto” para %x é especificado como unsigned int – e como você aponta, não há exceção para incompatibilidade entre assinados / não assinados aqui.

As regras para printf são para um caso mais específico e, portanto, sobrescrevem as regras para o caso geral (para outro exemplo da substituição específica do geral, é permitido em geral passar NULL para uma function esperando um argumento const char * , mas é indefinido comportamento para passar NULL para strlen() ).

Eu digo “tecnicamente”, porque acredito que uma implementação precisaria ser intencionalmente perversa para causar um problema para este caso, dadas as outras restrições no padrão.

Não, porque% x formata um int não assinado e o tipo da expressão constante 1 é int, enquanto o valor dele é expressável como um int não assinado. A operação não é UB.

É um comportamento indefinido, pela mesma razão que reinterpretar um ponteiro para um tipo inteiro para um tipo complementar de sinalização oposta. Isso não é permitido, infelizmente, nas duas direções, porque uma representação válida em uma delas pode ser uma implementação de trap na outra.

A única razão pela qual vejo que, da reinterpretação assinada para não assinada, pode haver uma representação de interceptação é esse caso pervertido de representação de sinal, em que o tipo não assinado apenas mascara o bit de sinal. Infelizmente, tal coisa é permitida a partir do 6.2.6.2 do padrão. Em tal arquitetura, todos os valores negativos do tipo assinado podem ser representações de interceptação do tipo não assinado.

No seu caso de exemplo, isso é ainda mais estranho, já que ter uma representação de trap para o tipo não assinado, por sua vez, não é permitido. Então, para torná-lo um exemplo “real”, você teria que fazer sua pergunta com um -1 .

Eu não acho que ainda haja qualquer arquitetura para a qual as pessoas escrevam compiladores C que possuam esses resources, então viver definitivamente seria mais fácil se uma versão mais nova do padrão pudesse abolir este caso desagradável.

Eu acredito que é indefinido. Funções com uma lista de argumentos de comprimento variável não têm uma conversão implícita ao aceitar argumentos, portanto, 1 não será convertido em unsigned int quando estiver passando para printf() , causando um comportamento indefinido.

Os autores do Padrão geralmente não tentam explicitamente ordenar o comportamento em todos os casos imagináveis, especialmente quando há um comportamento correto óbvio que é compartilhado por 100% de todas as implementações, e não há razão para esperar que qualquer implementação faça qualquer outra coisa. Apesar do requisito explícito do Padrão de que tipos assinados e não assinados têm representações de memory correspondentes para valores que se encheckboxm em ambos, seria teoricamente possível que uma implementação os passasse para funções variadicas de maneira diferente. O Padrão não proíbe tal comportamento, mas não vejo nenhuma evidência de que os autores o permitam intencionalmente. Provavelmente, eles simplesmente não consideraram tal possibilidade, uma vez que nenhuma implementação jamais funcionou dessa maneira (e até onde eu sei).

Provavelmente seria razoável que uma implementação de saneantes gritasse se o código usasse% x em um valor assinado, embora uma implementação de saneantes de qualidade também forneça uma opção para aceitar silenciosamente esse código. Não há razão para que implementações sãs façam qualquer outra coisa além de processar o valor passado como não assinado ou squawk se for usado em um modo de diagnóstico / saneamento. Embora o Padrão possa proibir uma implementação de considerar como inalcançável qualquer código que use% x em um valor assinado, qualquer um que pense que as implementações devem se valer dessa liberdade deve ser reconhecido como um idiota.

Programadores que estão visando implementações não-diagnósticas sãs não devem se preocupar em adicionar lançamentos ao produzir coisas como valores “uint8_t”, mas aqueles cujo código pode ser alimentado para implementações idiotas podem querer adicionar tais conversões para evitar compiladores do ” otimizações “tais implementações podem impor.