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 &argv
es de 8 bytes. Esperaba que la dirección argc
fuera, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8
pero 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.
c
memory-alignment
letmutx
fuente
fuente
main
.main
. En C,main
se puede llamar como una función regular, por lo que necesita recibir argumentos como una función regular y debe obedecer la ABI.%zu
Respuestas:
En su sistema, los primeros argumentos enteros o punteros se pasan en registros y no tienen direcciones. Cuando toma sus direcciones con
&argc
o&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.fuente
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
argv
es 12 bytes antes que la dirección deargc
, todavía no hay una forma definida de calcular uno de esos punteros a partir del otro.fuente
uintptr_t
, y ciertamente no define las relaciones entre las direcciones de los parámetros o dónde se pasan los argumentos.uintptr_t
podrí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 ...uintptr_t
que obtienes de una conversión no es necesariamente una dirección simple.(void *)(uintptr_t)(void *)p
se 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 ".uintptr_t
conversiones 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 ab
partir de ambos ,a
sino que se calcula ab
partir de ambosa
yb
, ya queb
debe usarse en la resta para calcular la cantidad para agregar Calcular uno del otro no está definido.