Por que os endereços argc e argv estão separados por 12 bytes?

40

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 argcfosse, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8mas 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.

letmutx
fonte
15
Por que não? Eles podem estar separados por 174 bytes. Uma resposta dependerá do seu sistema operacional e / ou de uma biblioteca de wrapper configurada main.
aschepler
2
@ Aschepler: não deve depender de nenhum wrapper configurado main. Em C, mainpode ser chamado como uma função regular, portanto, ele precisa receber argumentos como uma função regular e deve obedecer à ABI.
Eric Postpischil
@aschelper: Percebo o mesmo comportamento para outras funções também.
letmutx
4
É um "experimento mental" interessante, mas, na verdade, não há nada que deva ser mais do que um "eu me pergunto por quê". Esses endereços podem mudar dependendo do sistema operacional, compilador, versão do compilador, arquitetura do processador e de forma alguma devem ser considerados na 'vida real'.
Neil
2
o resultado de sizeof deve ser impresso usando%zu
phuclv

Respostas:

61

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 &argcou &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.

Eric Postpischil
fonte
6
Observe que isso pode acontecer mesmo se eles forem passados ​​na pilha ; o compilador não tem obrigação de usar o slot de valor recebido na pilha como armazenamento para os objetos locais em que os valores são inseridos. Pode fazer sentido fazer isso, pois a função acabará sendo chamada final e precisa dos valores atuais desses objetos para produzir os argumentos de saída para a chamada final.
R .. GitHub Pare de ajudar o gelo
10

Por que os endereços argc e argv estão separados por 12 bytes?

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 de argc, ainda não há uma maneira definida de calcular um desses ponteiros do outro.

John Bollinger
fonte
7
@ R..GitHubSTOPHELPINGICE: A computação de uma da outra é parcialmente definida, não bem definida. O padrão C não é rigoroso sobre como a conversão uintptr_té realizada e certamente não define os relacionamentos entre os endereços dos parâmetros ou onde os argumentos são passados.
Eric Postpischil
6
@ R..GitHubSTOPHELPINGICE: O fato de você poder fazer uma viagem de ida e volta significa que g (f (x)) = x, onde x é um ponteiro, f é convert-ponteiro em uintptr_t e g é convert-uintptr_t em ponteiro. Matematicamente e logicamente, não implica que g (f (x) +4) = x + 4. Por exemplo, se f (x) fosse x² eg (y) fossem sqrt (y), então g (f (x)) = x (para x não negativo real), mas g (f (x) +4) X + 4, em geral. No caso de ponteiros, a conversão para uintptr_tpode 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…
Eric Postpischil 09/02
5
... os bits de endereço. Ou a conversão para uintptr_t pode fornecer um endereço base nos 16 bits mais altos e um deslocamento nos 16 bits mais baixos, e adicionar 4 aos bits mais baixos pode levar aos bits mais altos, mas a escala está incorreta (porque o endereço representado não é deslocamento base 65536 +, mas deslocamento offset base 64+, como ocorreu em alguns sistemas). Muito simplesmente, o que uintptr_tvocê obtém de uma conversão não é necessariamente um endereço simples.
Eric Postpischil
4
@ R..GitHubSTOPHELPINGICE da minha leitura do padrão, há apenas uma garantia fraca que (void *)(uintptr_t)(void *)pserá 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 ".
Ryan Avella
5
@ R..GitHubSTOPHELPINGICE: Desculpe, eu perdi o fato de você estar adicionando um valor calculado como a diferença entre duas uintptr_tconversõ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 calcula bde, amas calcula bde ambos ae b, já que bdeve ser usado na subtração para calcular a quantidade adicionar. Computar um do outro não está definido.
Eric Postpischil 09/02