Impressão não assinada longa usando %d
-
20-09-2019 - |
Pergunta
Por que recebo -1 quando imprimir o seguinte?
unsigned long long int largestIntegerInC = 18446744073709551615LL;
printf ("largestIntegerInC = %d\n", largestIntegerInC);
Eu sei que devo usar llu
ao invés de d
, mas por que eu recebo -1 em vez de 18446744073709551615ll?
É por causa do transbordamento?
Solução
Em C (99), LLONG_MAX
, o valor máximo de long long int
Tipo é garantido pelo menos 9223372036854775807
. O valor máximo de um unsigned long long int
é garantido ser pelo menos 18446744073709551615
, que é 264−1 (0xffffffffffffffff
).
Portanto, a inicialização deve ser:
unsigned long long int largestIntegerInC = 18446744073709551615ULL;
(Note o ULL
.) Desde largestIntegerInC
é do tipo unsigned long long int
, você deve imprimi -lo com o especificador de formato certo, que é "%llu"
:
$ cat test.c
#include <stdio.h>
int main(void)
{
unsigned long long int largestIntegerInC = 18446744073709551615ULL;
/* good */
printf("%llu\n", largestIntegerInC);
/* bad */
printf("%d\n", largestIntegerInC);
return 0;
}
$ gcc -std=c99 -pedantic test.c
test.c: In function ‘main’:
test.c:9: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘long long unsigned int’
O segundo printf()
Acima está errado, pode imprimir qualquer coisa. Você está usando "%d"
, que significa printf()
está esperando um int
, mas recebe um unsigned long long int
, que é (provavelmente) não do mesmo tamanho que int
. A razão pela qual você está recebendo -1
Como sua saída se deve à sorte (ruim) e ao fato de que, em sua máquina, os números são representados usando a representação do complemento de dois.
Para ver como isso pode ser ruim, vamos executar o seguinte programa:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main(int argc, char *argv[])
{
const char *fmt;
unsigned long long int x = ULLONG_MAX;
unsigned long long int y = 42;
int i = -1;
if (argc != 2) {
fprintf(stderr, "Need format string\n");
return EXIT_FAILURE;
}
fmt = argv[1];
printf(fmt, x, y, i);
putchar('\n');
return 0;
}
No meu MacBook, executando o programa com "%d %d %d"
me dá -1 -1 42
, e em uma máquina Linux, o mesmo programa com o mesmo formato me dá -1 42 -1
. Opa.
De fato, se você está tentando armazenar o maior unsigned long long int
número em seu largestIntegerInC
variável, você deve incluir limits.h
E use ULLONG_MAX
. Ou você deve armazenar Assing -1
para sua variável:
#include <limits.h>
#include <stdio.h>
int main(void)
{
unsigned long long int largestIntegerInC = ULLONG_MAX;
unsigned long long int next = -1;
if (next == largestIntegerInC) puts("OK");
return 0;
}
No programa acima, ambos largestIntegerInC
e next
conter o maior valor possível para unsigned long long int
modelo.
Outras dicas
É porque você está passando um número com todos os bits definidos para 1. Quando interpretados como o número de dois complemento de dois, que resulta em -1. Nesse caso, provavelmente está apenas olhando para 32 desses bits em vez de todos os 64, mas isso não faz nenhuma diferença real.
Na aritmética do complemento de dois, o valor assinado -1 é o mesmo que o maior valor não assinado.
Considere os padrões de bits para números negativos no complemento de dois (estou usando números inteiros de 8 bits, mas o padrão se aplica independentemente do tamanho):
0 - 0x00
-1 - 0xFF
-2 - 0xFE
-3 - 0xFD
Portanto, você pode ver que o negativo 1 tem o padrão de bits de todos os 1, que também é o padrão de bits para o maior valor não assinado.
Você usou um formato para um número de 32 bits assinado, para ter -1. printf()
Não sei dizer internamente o tamanho do número que você passou, por isso apenas puxa os primeiros 32 bits da lista Varargs e os usa como o valor a ser impresso. Desde que você deu um formato assinado, ele o imprime e 0xffffffff é a representação do complemento dos dois de -1.
Você pode (deve) ver por que no aviso do compilador. Caso contrário, tente definir o nível de alerta mais alto. Com o VS, tenho este aviso: aviso C4245: 'Inicializando': conversão de '__int64' para '__int64 não assinada', incompatibilidade assinada/não assinada.
Não, não há transbordamento. É porque não está imprimindo todo o valor:
18446744073709551615 é o mesmo que 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff. Quando printf %d
Processos que, ele pega apenas 32 bits (ou 64 bits se for uma CPU de 64 bits) para conversão, e esses são o valor assinado -1.
Se o printf
Conversão foi %u
Em vez disso, mostraria 4294967295 (32 bits) ou 18446744073709551615 (64 bits).
Um estouro é quando um valor aumenta até o ponto em que não se encaixa no armazenamento alocado. Nesse caso, o valor é alocado Muito bem, mas não está sendo completamente recuperado.