Especificador de formato correto para imprimir ponteiro ou endereço?


192

Qual especificador de formato devo usar para imprimir o endereço de uma variável? Estou confuso entre o lote abaixo.

% u - número inteiro não assinado

% x - valor hexadecimal

% p - ponteiro nulo

Qual seria o formato ideal para imprimir um endereço?

Respostas:


239

A resposta mais simples, supondo que você não se importe com os caprichos e as variações de formato entre plataformas diferentes, é o padrão %p notação .

A norma C99 (ISO / IEC 9899: 1999) diz em §7.19.6.1.8:

pO argumento deve ser um ponteiro para void. O valor do ponteiro é convertido em uma sequência de caracteres de impressão, de maneira definida pela implementação.

(Em C11 - ISO / IEC 9899: 2011 - as informações estão em §7.21.6.1 ¶8.)

Em algumas plataformas, isso incluirá um líder 0xe, em outros, não, e as letras podem estar em minúsculas ou maiúsculas, e o padrão C nem mesmo define que deve ser uma saída hexadecimal, embora eu saiba nenhuma implementação onde não está.

É um tanto aberto discutir se você deve converter explicitamente os ponteiros com um (void *)elenco. Isso está sendo explícito, o que geralmente é bom (é o que eu faço), e o padrão diz 'o argumento deve ser um indicador void'. Na maioria das máquinas, você evitaria omitir uma conversão explícita. No entanto, seria importante em uma máquina onde a representação de bits de um char *endereço para um determinado local de memória seja diferente do ponteiro ' qualquer outra coisa endereço ' para o mesmo local de memória. Esta seria uma máquina endereçada por palavra, em vez de endereçada por byte. Atualmente, essas máquinas não são comuns (provavelmente não disponíveis), mas a primeira máquina em que trabalhei após a universidade foi uma delas (ICL Perq).

Se você não estiver satisfeito com o comportamento definido pela implementação de %p, use C99 <inttypes.h>e, em uintptr_tvez disso:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);

Isso permite que você ajuste a representação para se adequar a você. Eu escolhi ter os dígitos hexadecimais em maiúsculas para que o número seja uniformemente da mesma altura e a característica caia no início de 0xA1B2CDEFaparecer assim, não como o 0xa1b2cdefque também desce e desce ao longo do número. Sua escolha, porém, dentro de limites muito amplos. O (uintptr_t)elenco é inequivocamente recomendado pelo GCC quando pode ler a sequência de formato no tempo de compilação. Eu acho que é correto solicitar o elenco, embora eu tenha certeza de que há quem ignore o aviso e se livre dele na maior parte do tempo.


Kerrek pergunta nos comentários:

Estou um pouco confuso sobre promoções padrão e argumentos variados. Todos os ponteiros são promovidos por padrão para anular *? Caso contrário, se houvesse int*, digamos, dois bytes e void*quatro bytes, seria claramente um erro ler quatro bytes do argumento, não?

Eu estava sob a ilusão de que o padrão C diz que todos os ponteiros de objeto deve ser do mesmo tamanho, por isso void *e int *não podem ter tamanhos diferentes. No entanto, o que eu acho que é a seção relevante do padrão C99 não é tão enfático (embora eu não conheça uma implementação em que o que sugeri que seja verdadeiro seja realmente falso):

§6.2.5 Tipos

§26 Um ponteiro para anular deve ter os mesmos requisitos de representação e alinhamento que um ponteiro para um tipo de caractere. 39) Da mesma forma, os indicadores para versões qualificadas ou não qualificadas de tipos compatíveis devem ter os mesmos requisitos de representação e alinhamento. Todos os ponteiros para tipos de estrutura devem ter os mesmos requisitos de representação e alinhamento que os outros. Todos os ponteiros para tipos de união devem ter os mesmos requisitos de representação e alinhamento que os outros. Ponteiros para outros tipos não precisam ter os mesmos requisitos de representação ou alinhamento.

39) Os mesmos requisitos de representação e alinhamento significam intercambialidade como argumentos para funções, retornam valores de funções e membros de sindicatos.

(C11 diz exatamente o mesmo na seção §6.2.5, §28 e nota de rodapé 48.)

Portanto, todos os ponteiros para estruturas devem ter o mesmo tamanho um do outro e devem compartilhar os mesmos requisitos de alinhamento, mesmo que as estruturas apontadas pelos ponteiros possam ter requisitos de alinhamento diferentes. Da mesma forma para os sindicatos. Ponteiros de caracteres e ponteiros nulos devem ter os mesmos requisitos de tamanho e alinhamento. Os indicadores para variações em int(significado unsigned inte signed int) devem ter os mesmos requisitos de tamanho e alinhamento que os outros; da mesma forma para outros tipos. Mas o padrão C não diz isso formalmente sizeof(int *) == sizeof(void *). Bem, SO é bom para fazer você inspecionar suas suposições.

O padrão C definitivamente não requer que os ponteiros de função tenham o mesmo tamanho dos ponteiros de objeto. Isso foi necessário para não quebrar os diferentes modelos de memória em sistemas do tipo DOS. Lá você pode ter ponteiros de dados de 16 bits, mas ponteiros de função de 32 bits, ou vice-versa. É por isso que o padrão C não exige que os ponteiros de função possam ser convertidos em ponteiros de objeto e vice-versa.

Felizmente (para programadores direcionados ao POSIX), o POSIX entra na brecha e exige que ponteiros de função e ponteiros de dados sejam do mesmo tamanho:

§2.12.3 Tipos de ponteiros

Todos os tipos de ponteiros de função devem ter a mesma representação que o ponteiro de tipo a ser anulado. A conversão de um ponteiro de função para void *não deve alterar a representação. Um void *valor resultante dessa conversão pode ser convertido novamente no tipo de ponteiro da função original, usando uma conversão explícita, sem perda de informações.

Nota: O padrão ISO C não exige isso, mas é necessário para conformidade com POSIX.

Portanto, parece que os lançamentos explícitos void *são altamente recomendados para a máxima confiabilidade no código ao passar um ponteiro para uma função variável, como printf(). Nos sistemas POSIX, é seguro converter um ponteiro de função em um ponteiro nulo para impressão. Em outros sistemas, não é necessariamente seguro fazer isso, nem é necessariamente seguro passar outros indicadores que void *não sejam vazados.


3
Estou um pouco confuso sobre promoções padrão e argumentos variados. Todos os ponteiros são promovidos a padrão void*? Caso contrário, se houvesse int*, digamos, dois bytes e void*quatro bytes, seria claramente um erro ler quatro bytes do argumento, não?
Kerrek SB

Observe que uma atualização para o POSIX (POSIX 2013) removeu a seção 2.12.3, movendo a maioria dos requisitos para a dlsym()função. Um dia vou escrever a mudança ... mas 'um dia' não é 'hoje'.
Jonathan Leffler

Esta resposta também se aplica a ponteiros para funções? Eles podem ser convertidos para void *? Hmm, vejo seu comentário aqui . Como é necessária apenas a conversão de um wat (ponteiro de função para void *), ela funciona?
chux - Restabelece Monica

@chux: Estritamente, a resposta é 'não', mas na prática a resposta é 'sim'. O padrão C não garante que os ponteiros de função possam ser convertidos em void *retorno e retorno sem perda de informações. Pragmaticamente, existem muito poucas máquinas nas quais o tamanho de um ponteiro de função não é o mesmo que o tamanho de um ponteiro de objeto. Não acho que o padrão forneça um método para imprimir um ponteiro de função em máquinas onde a conversão é problemática.
Jonathan Leffler

"e voltar sem perda de informações" não é relevante para a impressão. Isso ajuda?
chux - Restabelece Monica

50

pé o especificador de conversão para imprimir ponteiros. Usa isto.

int a = 42;

printf("%p\n", (void *) &a);

Lembre-se de que a omissão da pconversão é um comportamento indefinido e que a impressão com o especificador de conversão é feita de uma maneira definida pela implementação.


2
Perdão, por que omitir o elenco é "comportamento indefinido"? Esse assunto importa qual variável é, se tudo que você precisa é o endereço, não o valor?
Valdo

9
@valdo porque C diz isso (C99, 7.19.6.1p8) "p O argumento deve ser um ponteiro para anular".
OuJan

12
@valdo: Não é necessariamente o caso de todos os ponteiros terem o mesmo tamanho / representação.
caf

32

Use %p, para "ponteiro", e não use mais nada *. Você não está garantido pelo padrão de que pode tratar um ponteiro como qualquer tipo particular de número inteiro; portanto, obteria um comportamento indefinido com os formatos integrais. (Por exemplo, %uespera um unsigned int, mas e se void*tiver um tamanho ou requisito de alinhamento diferente unsigned int?)

*) [Veja a boa resposta de Jonathan!] Como alternativa %p, você pode usar macros específicas de ponteiro de <inttypes.h>, adicionadas em C99.

Todos os ponteiros de objetos são implicitamente conversíveis void*em C, mas, para passar o ponteiro como um argumento variável, você deve convertê- lo explicitamente (já que ponteiros de objetos arbitrários são apenas conversíveis , mas não idênticos aos ponteiros nulos):

printf("x lives at %p.\n", (void*)&x);

2
Todos os ponteiros de objetos são conversíveis em void *(embora printf()você precise tecnicamente da conversão explícita, pois é uma função variável). Ponteiros de função não são necessariamente conversíveis em void *.
caf

@caf: Ah, eu não sabia dos argumentos variados - corrigidos! Obrigado!
Kerrek SB

2
O padrão C não exige que os ponteiros de função sejam conversíveis void *e retornem ao ponteiro de função sem perda; felizmente, porém, o POSIX exige explicitamente isso (observando que não faz parte do padrão C). Assim, na prática, você pode ir longe com ele (a conversão void (*function)(void)para void *e de volta para void (*function)(void)), mas estritamente não está mandatado pelo padrão C.
31812 Jonathan Leffler

2
Jonathan e R .: Tudo isso é muito interessante, mas tenho certeza de que não estamos tentando imprimir ponteiros de função aqui, então talvez este não seja o lugar certo para discutir isso. Prefiro ver aqui algum apoio para minha insistência em não usar %u!
Kerrek SB

2
%ue %luestão errados em todas as máquinas , não em algumas máquinas. A especificação de printfé muito clara: quando o tipo passado não corresponde ao tipo exigido pelo especificador de formato, o comportamento é indefinido. Se o tamanho dos tipos corresponde (que pode ser verdadeiro ou falso, dependendo da máquina) é irrelevante; são os tipos que devem corresponder e nunca serão.
R .. GitHub Pare de ajudar o gelo

9

Como uma alternativa para os outros (muito bom) respostas, você pode converter para uintptr_tou intptr_t(de stdint.h/ inttypes.h) e use os especificadores de conversão inteiro correspondente. Isso permitiria mais flexibilidade na formatação do ponteiro, mas estritamente falando, uma implementação não é necessária para fornecer esses typedefs.


considerar que #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } é um comportamento indefinido para imprimir o endereço da variável usando o %uespecificador de formato? O endereço da variável na maioria dos casos é positivo, então posso usar em %uvez de %p?
destructor

1
@ Destructor: Não, %ué um formato para o unsigned inttipo e não pode ser usado com um argumento de ponteiro para printf.
R .. GitHub Pare de ajudar o gelo

-1

Você pode usar %xou %Xou %p; todos eles estão corretos.

  • Se você usar %x, o endereço será fornecido em minúsculas, por exemplo:a3bfbc4
  • Se você usar %X, o endereço será fornecido em maiúsculas, por exemplo:A3BFBC4

Ambos estão corretos.

Se você usar %xou %Xconsiderar seis posições para o endereço, e se você usar %p, considerar oito posições para o endereço. Por exemplo:


1
Bem-vindo ao SO. Reserve um tempo para analisar as outras respostas, pois elas estão explicando claramente vários detalhes que você está ignorando.
AntoineL 18/02
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.