Como se pode imprimir uma variável size_t de forma portável, usando a família printf?

405

Eu tenho uma variável do tipo size_te quero imprimi-la usando printf(). Qual especificador de formato eu uso para imprimi-lo portably?

Na máquina de 32 bits, %uparece certo. Eu compilei com g++ -g -W -Wall -Werror -ansi -pedantic, e não houve aviso. Mas quando eu compilo esse código em uma máquina de 64 bits, ele produz um aviso.

size_t x = <something>;
printf("size = %u\n", x);

warning: format '%u' expects type 'unsigned int', 
    but argument 2 has type 'long unsigned int'

O aviso desaparece, como esperado, se eu mudar para %lu.

A questão é: como posso escrever o código, para que ele seja compilado sem aviso em máquinas de 32 e 64 bits?

Edit: Como solução alternativa, acho que uma resposta pode ser "converter" a variável em um número inteiro que seja grande o suficiente, digamos unsigned long, e imprima usando %lu. Isso funcionaria nos dois casos. Estou procurando se há alguma outra idéia.

Uma corrida
fonte
4
converter para unsigned longé a melhor opção se sua implementação da libc não suportar o zmodificador; o padrão C99 recomenda size_tnão ter um número inteiro de conversão classificação maior do que long, assim que você é razoavelmente seguro
Christoph
possível duplicata de size_t independente
precisa saber é o seguinte
11
Na plataforma Windows, size_t pode ser maior que o comprimento. Por motivos de compatibilidade, o comprimento é sempre de 32 bits, mas size_t pode ser de 64 bits. Portanto, converter para não assinado por muito tempo pode perder metade dos bits. Desculpe :-)
Bruce Dawson

Respostas:

483

Use o zmodificador:

size_t x = ...;
ssize_t y = ...;
printf("%zu\n", x);  // prints as unsigned decimal
printf("%zx\n", x);  // prints as hex
printf("%zd\n", y);  // prints as signed decimal
Adam Rosenfield
fonte
7
+1. Isso é uma adição ao C99 ou também se aplica ao C ++ (não tenho o C90 à mão)?
avakar
6
é uma adição ao C99 e não aparece na lista de printf()modificadores de comprimento do rascunho do C ++ 0x de 09/11/2009 (tabela 84 na página 672) #
Christoph
3
@Christoph: Nem está no último rascunho, n3035.
GManNickG 26/03
11
@avakar @Adam Rosenfield @Christoph @GMan: No entanto, no n3035 §1.2 Referências normativas, apenas o padrão C99 é referenciado e §17.6.1.2 / 3 dos mesmos estados "As instalações da biblioteca padrão C são fornecidas." Eu interpretaria isso como significando que, a menos que especificado de outra forma, tudo na biblioteca padrão C99 faz parte da biblioteca padrão C ++ 0x, incluindo os especificadores de formato adicionais em C99.
James McNellis 28/03/10
9
@ArunSaha: É um recurso apenas do C99, não do C ++. Se você deseja compilar -pedantic, precisará obter um compilador que suporte o rascunho C ++ 1x (altamente improvável) ou mover seu código para um arquivo compilado como C99. Caso contrário, sua única opção é converter suas variáveis unsigned long longe usá %llu-las para ser maximamente portátil.
Adam Rosenfield
88

Parece que varia de acordo com o compilador que você está usando (blech):

... e, claro, se você estiver usando C ++, poderá usar o coutque é sugerido pelo AraK .

TJ Crowder
fonte
3
ztambém é suportado por newlib (ie cygwin) #
Christoph
7
%zdestá incorreto para size_t; está correto para o tipo assinado correspondente a size_t, mas em size_tsi é um tipo não assinado.
Keith Thompson
11
@ KeithThompson: Eu mencionei %zutambém (e %zxno caso de eles quererem hex). É verdade que %zuprovavelmente deveria ter sido o primeiro da lista. Fixo.
TJ Crowder
10
@TJCrowder: Eu acho que não %zddeveria estar na lista. Não consigo pensar em nenhum motivo para usar, em %zdvez de %zuimprimir um size_tvalor. Nem é válido (tem comportamento indefinido) se o valor exceder SIZE_MAX / 2. (Para completar, você pode mencionar %zopara octal.)
Keith Thompson
2
@FUZxxl: O POSIX não exige que esse ssize_tseja o tipo assinado correspondente size_t, portanto, não é garantido que ele corresponda "%zd". ( Provavelmente está na maioria das implementações.) Pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Keith Thompson
59

Para C89, use %lue converta o valor para unsigned long:

size_t foo;
...
printf("foo = %lu\n", (unsigned long) foo);

Para C99 e versões posteriores, use %zu:

size_t foo;
...
printf("foo = %zu\n", foo);
John Bode
fonte
7
Considerando 2013, sugira "Para C99 em diante" e "Para pré C99:". Melhor resposta.
Chux - Reintegrar Monica
8
Não faça isso. Ele falhará no Windows de 64 bits, em que size_t é de 64 bits e longo é de 32 bits.
Yttrill
11
@ Yttrill: Qual é a resposta para janelas de 64 bits, então?
John Bode
11
@JohnBode Maybe unsigned long long?
James Ko
2
Ou: você pode converter para ae uint64_tusar a PRIu64macro de inttypes.h, que contém o especificador de formato.
James Ko
9

Ampliando a resposta de Adam Rosenfield para Windows.

Testei esse código nas versões VS2013 Update 4 e VS2015:

// test.c

#include <stdio.h>
#include <BaseTsd.h> // see the note below

int main()
{
    size_t x = 1;
    SSIZE_T y = 2;
    printf("%zu\n", x);  // prints as unsigned decimal
    printf("%zx\n", x);  // prints as hex
    printf("%zd\n", y);  // prints as signed decimal
    return 0;
}

Saídas binárias geradas pelo VS2015:

1
1
2

enquanto o gerado pelo VS2013 diz:

zu
zx
zd

Nota: ssize_té uma extensão POSIX e SSIZE_Té semelhante nos tipos de dados do Windows , portanto, adicionei <BaseTsd.h>referência.

Além disso, exceto os seguintes cabeçalhos C99 / C11, todos os cabeçalhos C99 estão disponíveis na visualização do VS2015:

C11 - <stdalign.h>
C11 - <stdatomic.h>
C11 - <stdnoreturn.h>
C99 - <tgmath.h>
C11 - <threads.h>

Além disso, o C11 <uchar.h>agora está incluído na última visualização.

Para mais detalhes, consulte esta lista antiga e nova para conformidade padrão.

corvo vulcano
fonte
A Atualização 5 do VS2013 produz os mesmos resultados que a Atualização 4 deu a você.
Nathan Kidd
6

Para aqueles que falam sobre fazer isso em C ++, que não necessariamente suporta as extensões C99, recomendo vivamente o formato boost ::. Isso torna a questão size_t type size discutível:

std::cout << boost::format("Sizeof(Var) is %d\n") % sizeof(Var);

Como você não precisa de especificadores de tamanho no formato boost ::, você pode se preocupar com como deseja exibir o valor.

swestrup
fonte
4
Provavelmente quer %uentão.
GManNickG 26/03/10
5
std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!
AraK
fonte
8
Sim, mas o interlocutor pede especificamente um printfespecificador. Eu acho que eles têm algumas outras restrições não declaradas que tornam o uso de std::coutum problema.
Donal Fellows
11
@ Donal Gostaria de saber que tipo de problema os fluxos C ++ poderiam criar em um projeto C ++!
AraK 26/03
9
@AraK. Eles são muito lentos? Eles adicionam muitos bytes sem muita razão. ArunSaha só quer saber por seu próprio conhecimento pessoal? Preferência pessoal (prefiro o stdio a transmitir-me). Existem muitas razões.
KitsuneYMG
11
@TKCrowder: Bem, a solicitação original dizia que era necessária uma solução C (por meio de marcação) e há boas razões para não usar fluxos em C ++, por exemplo, se o descritor de formato de saída estiver sendo extraído de um catálogo de mensagens. (Você poderia escrever um analisador para mensagens e uso córregos se você queria, mas isso é um monte de trabalho quando você pode apenas alavancagem código existente.)
Donal Fellows
11
@Donal: As tags foram C e C ++. Eu não estou de forma alguma defendendo as coisas de fluxo de E / S do C ++ (não sou fã disso), apenas apontando que a pergunta originalmente não "" ... solicitava especificações para um printfespecificador ".
TJ Crowder
5
printf("size = %zu\n", sizeof(thing) );
nategoose
fonte
2

Como AraK disse, a interface de fluxos c ++ sempre funcionará de maneira portável.

std :: size_t s = 1024; std :: cout << s; // ou qualquer outro tipo de stream como stringstream!

Se você deseja o C stdio, não há resposta portátil para isso em certos casos de "portable". E fica feio, pois, como você viu, escolher os sinalizadores de formato errado pode gerar um aviso do compilador ou gerar uma saída incorreta.

O C99 tentou resolver esse problema com formatos inttypes.h como "%" PRIdMAX "\ n". Mas, assim como em "% zu", nem todos são compatíveis com o c99 (como o MSVS anterior a 2013). Existem arquivos "msinttypes.h" flutuando para lidar com isso.

Se você converter para um tipo diferente, dependendo dos sinalizadores, poderá receber um aviso do compilador para truncamento ou alteração de sinal. Se você seguir esta rota, escolha um tipo de tamanho fixo relevante maior. Um de muito longo não assinado e "% llu" ou um longo "% lu" não assinado deve funcionar, mas o llu também pode desacelerar as coisas em um mundo de 32 bits como excessivamente grande. (Editar - meu mac emite um aviso em 64 bits para% llu que não corresponde a size_t, mesmo que% lu,% llu e size_t sejam todos do mesmo tamanho. E% lu e% llu não são do mesmo tamanho no meu MSVS2012. pode ser necessário transmitir + usar um formato que corresponda.)

Nesse caso, você pode usar tipos de tamanho fixo, como int64_t. Mas espere! Agora voltamos ao c99 / c ++ 11, e o MSVS mais antigo falha novamente. Além disso, você também tem lançamentos (por exemplo, map.size () não é do tipo de tamanho fixo)!

Você pode usar um cabeçalho ou biblioteca de terceiros, como aumento. Se você ainda não estiver usando um, talvez não queira aumentar seu projeto dessa maneira. Se você deseja adicionar um apenas para esse problema, por que não usar fluxos c ++ ou compilação condicional?

Então, você deve usar fluxos c ++, compilação condicional, estruturas de terceiros ou algo portátil que funcione para você.

Rick Berge
fonte
-1

Será avisado se você passar um número inteiro não assinado de 32 bits para um formato% lu? Deve ser bom, pois a conversão é bem definida e não perde nenhuma informação.

Ouvi dizer que algumas plataformas definem macros em <inttypes.h>que você pode inserir na string de formato literal, mas não vejo esse cabeçalho no meu compilador Windows C ++, o que implica que pode não ser de plataforma cruzada.

Kylotan
fonte
11
A maioria dos compiladores não avisa se você passar algo do tamanho errado para o printf. O GCC é uma exceção. inttypes.h foi definido no C99, portanto, qualquer compilador C compatível com C99 o terá, que deve ser todos eles agora. Ainda assim, talvez você precise ativar o C99 com um sinalizador de compilador. De qualquer forma, intttypes.h não define um formato específico para size_t ou ptrdiff_t, pois eles decidiram ser importantes o suficiente para obter seus próprios especificadores de tamanho de 'z' e 't', respectivamente.
swestrup
Se você usar %lu, deve converter o size_tvalor para unsigned long. Não há conversão implícita (além de promoções) para argumentos para printf.
Keith Thompson
-2

C99 define "% zd" etc. para isso. (obrigado aos comentaristas) Não há um especificador de formato portátil para isso em C ++ - você pode usar %pqual palavra do woulkd nesses dois cenários, mas também não é uma opção portátil, e fornece o valor em hexadecimal.

Como alternativa, use algum streaming (por exemplo, stringstream) ou uma substituição de impressão segura, como o Boost Format . Entendo que este conselho é apenas de uso limitado (e requer C ++). (Utilizamos uma abordagem semelhante adaptada às nossas necessidades ao implementar o suporte unicode.)

O problema fundamental para C é que printf usando reticências é inseguro por design - ele precisa determinar o tamanho do argumento adicional a partir dos argumentos conhecidos, portanto não pode ser corrigido para suportar "o que você obtiver". Portanto, a menos que seu compilador implemente algumas extensões proprietárias, você estará sem sorte.

peterchen
fonte
2
o zmodidfier tamanho é padrão C, mas algumas implementações libc está preso em 1990 por várias razões (por exemplo, Microsoft, basicamente abandonada C em favor do C ++ e - mais recentemente - C #)
Christoph
3
C99 definiu o especificador de tamanho 'z' como o tamanho de um valor size_t e 't' como o tamanho de um valor ptrdiff_t.
swestrup
2
%zdestá errado, não está assinado, por isso deveria estar %zu.
David Conrad
-3

Em algumas plataformas e para alguns tipos, existem especificadores específicos de conversão de impressão disponíveis, mas às vezes é preciso recorrer à conversão para tipos maiores.

Documentei esse problema complicado aqui, com o código de exemplo: http://www.pixelbeat.org/programming/gcc/int_types/ e atualizo-o periodicamente com informações sobre novas plataformas e tipos.

pixelbeat
fonte
11
Observe que as respostas somente de link são desencorajadas; as respostas SO devem ser o ponto final de uma pesquisa por uma solução (em comparação com outra parada de referências, que tendem a ficar obsoletas ao longo do tempo). Considere adicionar aqui uma sinopse independente, mantendo o link como referência.
22413 kleopatra
-5

se você deseja imprimir o valor de size_t como uma string, você pode fazer isso:

char text[] = "Lets go fishing in stead of sitting on our but !!";
size_t line = 2337200120702199116;

/* on windows I64x or I64d others %lld or %llx if it works %zd or %zx */
printf("number: %I64d\n",*(size_t*)&text);
printf("text: %s\n",*(char(*)[])&line);

O resultado é:

número: 2337200120702199116

texto: Vamos pescar em vez de sentar no nosso mas !!

Edit: relendo a questão por causa dos votos negativos, observei que o problema dele não é% llu ou% I64d, mas o tipo size_t em máquinas diferentes, consulte esta pergunta https://stackoverflow.com/a/918909/1755797
http: // www. cplusplus.com/reference/cstdio/printf/

size_t é int não assinado em uma máquina de 32 bits e não possui um longo e longo prazo em 64 bits,
mas% ll sempre espera um int longo e não assinado.

size_t varia em diferentes sistemas operacionais, enquanto% llu é o mesmo

Andre
fonte
4
Que bobagem é essa ?!
Antti Haapala 28/05
converter os primeiros 8 bytes da matriz char em um longo e sem bits de 64 bits através do ponteiro size_t e imprimi-los como número com o printf% I64d não é realmente espetacular, eu sei, é claro que não fiz o código para evitar o estouro de texto, mas isso não está no escopo da pergunta.
Andre