¿Por qué las direcciones de argc y argv están separadas por 12 bytes?

40

Ejecuté el siguiente programa en mi computadora (Intel de 64 bits con 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;
}

La salida del programa fue

$ 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

El tamaño del puntero &argves de 8 bytes. Esperaba que la dirección argcfuera, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8pero hay un relleno de 4 bytes entre ellos. ¿Por qué es este el caso?

Supongo que podría deberse a la alineación de la memoria, pero no estoy seguro.

Noto el mismo comportamiento con las funciones que llamo también.

letmutx
fuente
15
Por qué no? Podrían estar separados por 174 bytes. Una respuesta dependerá de su sistema operativo y / o una biblioteca de contenedor que sí esté configurada main.
Aschepler
2
@aschepler: no debe depender de ningún contenedor que sí se configure main. En C, mainse puede llamar como una función regular, por lo que necesita recibir argumentos como una función regular y debe obedecer la ABI.
Eric Postpischil
@aschelper: también noto el mismo comportamiento para otras funciones.
letmutx
44
Es un interesante 'experimento mental', pero en realidad, no hay nada que deba ser más que un 'Me pregunto por qué'. Estas direcciones pueden cambiar según el sistema operativo, el compilador, la versión del compilador, la arquitectura del procesador y de ninguna manera se debe depender de ellas en la "vida real".
Neil
2
el resultado de sizeof debe imprimirse usando%zu
phuclv

Respuestas:

61

En su sistema, los primeros argumentos enteros o punteros se pasan en registros y no tienen direcciones. Cuando toma sus direcciones con &argco &argv, el compilador tiene que fabricar direcciones escribiendo los contenidos del registro en ubicaciones de pila y proporcionándole las direcciones de esas ubicaciones de pila. Al hacerlo, el compilador elige, en cierto sentido, cualquier ubicación de pila que sea conveniente para él.

Eric Postpischil
fuente
66
Tenga en cuenta que esto podría suceder incluso si se pasan en la pila ; el compilador no tiene la obligación de usar la ranura de valor entrante en la pila como almacenamiento para los objetos locales en los que entran los valores. Podría tener sentido hacer esto, ya que la función eventualmente va a llamar a la cola y necesita los valores actuales de estos objetos para producir los argumentos salientes para la llamada de cola.
R .. GitHub DEJA DE AYUDAR AL HIELO
10

¿Por qué las direcciones de argc y argv están separadas por 12 bytes?

Desde la perspectiva del estándar de lenguaje, la respuesta es "no hay razón particular". C no especifica ni implica ninguna relación entre las direcciones de los parámetros de la función. @EricPostpischil describe lo que probablemente está sucediendo en su implementación particular, pero esos detalles serían diferentes para una implementación en la que se pasan todos los argumentos en la pila, y esa no es la única alternativa.

Además, tengo problemas para encontrar una forma en que dicha información pueda ser útil dentro de un programa. Por ejemplo, incluso si "sabe" que la dirección de argves 12 bytes antes que la dirección de argc, todavía no hay una forma definida de calcular uno de esos punteros a partir del otro.

John Bollinger
fuente
77
@ R..GitHubSTOPHELPINGICE: Calcular uno del otro está parcialmente definido, no está bien definido. El estándar C no es estricto sobre cómo se realiza la conversión uintptr_t, y ciertamente no define las relaciones entre las direcciones de los parámetros o dónde se pasan los argumentos.
Eric Postpischil
66
@ R..GitHubSTOPHELPINGICE: el hecho de que puede hacer un viaje de ida y vuelta significa que g (f (x)) = x, donde x es un puntero, f es convert-puntero-a-uintptr_t, y g es convert-uintptr_t-to -puntero. Matemáticamente y lógicamente, no implica que g (f (x) +4) = x + 4. Por ejemplo, si f (x) fuera x² yg (y) fuera sqrt (y), entonces g (f (x)) = x (para x no negativo real), pero g (f (x) +4) ≠ x + 4, en general. En el caso de los punteros, la conversión a uintptr_tpodría dar una dirección en los 24 bits altos y algunos bits de autenticación en los 8 bits bajos. Luego, agregar 4 simplemente arruina la autenticación; no se actualiza ...
Eric Postpischil
55
... los bits de dirección. O la conversión a uintptr_t podría dar una dirección base en los 16 bits altos y un desplazamiento en los 16 bits bajos, y agregar 4 a los bits bajos podría llevar a los bits altos, pero la escala es incorrecta (porque la dirección representada no es base • 65536 + desplazamiento, sino más bien base • 64 + desplazamiento, como en algunos sistemas). En pocas palabras, lo uintptr_tque obtienes de una conversión no es necesariamente una dirección simple.
Eric Postpischil
44
@ R..GitHubSTOPHELPINGICE desde mi lectura del estándar, solo hay una garantía débil que (void *)(uintptr_t)(void *)pse comparará igual a (void *)p. Y vale la pena señalar que el comité ha comentado casi sobre este tema exacto, concluyendo que "las implementaciones ... también pueden tratar los punteros basados ​​en diferentes orígenes como distintos a pesar de que son idénticos a nivel de bits ".
Ryan Avella
55
@ R..GitHubSTOPHELPINGICE: Lo siento, me perdí que estaba agregando un valor calculado como la diferencia de dos uintptr_tconversiones de direcciones en lugar de una diferencia de punteros o una distancia "conocida" en bytes. Claro, eso es cierto, pero ¿cómo es útil? Sigue siendo cierto que "todavía no hay una forma definida de calcular uno de esos punteros a partir del otro" como dice la respuesta, pero ese cálculo no calcula a bpartir de ambos , asino que se calcula a bpartir de ambos ay b, ya que bdebe usarse en la resta para calcular la cantidad para agregar Calcular uno del otro no está definido.
Eric Postpischil