Eu executei o seguinte programa no meu computador (Intel de 64 bits executando Linux).
#include <stdio.h>
void test(int argc, char **argv) {
printf("[test] Argc Pointer: %p\n", &argc);
printf("[test] Argv Pointer: %p\n", &argv);
}
int main(int argc, char **argv) {
printf("Argc Pointer: %p\n", &argc);
printf("Argv Pointer: %p\n", &argv);
printf("Size of &argc: %lu\n", sizeof (&argc));
printf("Size of &argv: %lu\n", sizeof (&argv));
test(argc, argv);
return 0;
}
A saída do programa foi
$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20
O tamanho do ponteiro &argv
é 8 bytes. Eu esperava que o endereço argc
fosse, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8
mas há um preenchimento de 4 bytes entre eles. Por que esse é o caso?
Meu palpite é que isso pode ocorrer devido ao alinhamento da memória, mas não tenho certeza.
Percebo o mesmo comportamento com as funções que chamo também.
c
memory-alignment
letmutx
fonte
fonte
main
.main
. Em C,main
pode ser chamado como uma função regular, portanto, ele precisa receber argumentos como uma função regular e deve obedecer à ABI.%zu
Respostas:
No seu sistema, os primeiros argumentos de número inteiro ou ponteiro são passados nos registradores e não têm endereços. Quando você pega seus endereços com
&argc
ou&argv
, o compilador precisa fabricar endereços, escrevendo o conteúdo do registro para empilhar os locais e fornecendo os endereços desses locais. Ao fazer isso, o compilador escolhe, em certo sentido, quaisquer locais de pilha que sejam convenientes para ele.fonte
Da perspectiva do padrão de linguagem, a resposta é "nenhuma razão específica". C não especifica ou implica qualquer relação entre os endereços dos parâmetros de função. O @EricPostpischil descreve o que provavelmente está acontecendo em sua implementação específica, mas esses detalhes seriam diferentes para uma implementação na qual todos os argumentos são passados na pilha, e essa não é a única alternativa.
Além disso, estou tendo problemas para encontrar uma maneira pela qual essas informações possam ser úteis em um programa. Por exemplo, mesmo se você "souber" que o endereço de
argv
é 12 bytes antes do endereço deargc
, ainda não há uma maneira definida de calcular um desses ponteiros do outro.fonte
uintptr_t
é realizada e certamente não define os relacionamentos entre os endereços dos parâmetros ou onde os argumentos são passados.uintptr_t
pode fornecer um endereço nos 24 bits mais altos e alguns bits de autenticação nos 8 bits mais baixos. Em seguida, adicionar 4 apenas estraga a autenticação; não atualiza…uintptr_t
você obtém de uma conversão não é necessariamente um endereço simples.(void *)(uintptr_t)(void *)p
será comparada a(void *)p
. E vale a pena notar que o comitê comentou quase exatamente essa questão, concluindo que "implementações ... também podem tratar indicadores com base em origens diferentes como distintos, mesmo que sejam bit a bit idênticos ".uintptr_t
conversões de endereços, em vez de indicadores diferentes ou uma distância "conhecida" em bytes. Claro, isso é verdade, mas como é útil? Continua sendo verdade que "ainda não há uma maneira definida de calcular um desses ponteiros do outro", como a resposta indica, mas esse cálculo não calculab
de,a
mas calculab
de ambosa
eb
, já queb
deve ser usado na subtração para calcular a quantidade adicionar. Computar um do outro não está definido.