tipos tipo retorno propias otra matematicas llamar lista invocacion funciones funcion dentro definir dato c x86 stack argument-passing calling-convention

c - propias - tipo de dato retorno



convención de llamadas x86: ¿deberían los argumentos pasados ​​por la pila ser de solo lectura? (3)

El lenguaje de programación de C exige que los argumentos se pasen por valor . De modo que cualquier modificación de un argumento (como un x++; como la primera instrucción de su foo ) es local para la función y no se propaga al llamador.

Por lo tanto, una convención de convocatoria general debería requerir la copia de argumentos en cada sitio de llamadas. Las convenciones de llamada deberían ser lo suficientemente generales para llamadas desconocidas , por ejemplo, a través de un puntero de función.

Por supuesto, si pasa una dirección a alguna zona de memoria, la función llamada puede desreferenciar ese puntero, por ejemplo, como en

int goo(int *x) { static int count; *x = count++; return count % 3; }

Por cierto, puede usar optimizaciones de tiempo de enlace (compilando y enlazando con clang -flto -O2 o gcc -flto -O2 ) para quizás permitir al compilador mejorar o alinear algunas llamadas entre unidades de traducción.

Tenga en cuenta que tanto Clang / LLVM como GCC son compiladores de software libre . Siéntase libre de proponerles un parche de mejora si lo desea (pero dado que ambos son piezas de software muy complejas, tendrá que trabajar algunos meses para hacer ese parche).

NÓTESE BIEN. Al buscar el código ensamblado producido, pase -fverbose-asm a su compilador.

Parece que los compiladores de última generación tratan los argumentos aprobados por la pila como de solo lectura. Tenga en cuenta que en la convención de llamadas x86, la persona que llama empuja los argumentos a la pila y el destinatario usa los argumentos en la pila. Por ejemplo, el siguiente código C:

extern int goo(int *x); int foo(int x, int y) { goo(&x); return x; }

se compila mediante clang -O3 -c gc -S -m32 en OS X 10.10 en:

.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 10 .globl _foo .align 4, 0x90 _foo: ## @foo ## BB#0: pushl %ebp movl %esp, %ebp subl $8, %esp movl 8(%ebp), %eax movl %eax, -4(%ebp) leal -4(%ebp), %eax movl %eax, (%esp) calll _goo movl -4(%ebp), %eax addl $8, %esp popl %ebp retl .subsections_via_symbols

Aquí, el parámetro x ( 8(%ebp) ) se carga primero en %eax ; y luego se almacena en -4(%ebp) ; y la dirección -4(%ebp) se almacena en %eax ; y %eax se pasa a la función goo .

Me pregunto por qué Clang genera código que copia el valor almacenado en 8(%ebp) a -4(%ebp) , en lugar de simplemente pasar la dirección 8(%ebp) a la función goo . Ahorrará las operaciones de memoria y dará como resultado un mejor rendimiento. Observé un comportamiento similar en GCC también (bajo OS X). Para ser más específico, me pregunto por qué los compiladores no generan:

.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 10 .globl _foo .align 4, 0x90 _foo: ## @foo ## BB#0: pushl %ebp movl %esp, %ebp subl $8, %esp leal 8(%ebp), %eax movl %eax, (%esp) calll _goo movl 8(%ebp), %eax addl $8, %esp popl %ebp retl .subsections_via_symbols

Busqué documentos si la convención de llamadas x86 exige que los argumentos pasados ​​sean de solo lectura, pero no pude encontrar nada sobre el tema. ¿Alguien tiene alguna idea sobre este tema?


En realidad, acabo de compilar esta función usando GCC:

int foo(int x) { goo(&x); return x; }

Y generó este código:

_foo: pushl %ebp movl %esp, %ebp subl $24, %esp leal 8(%ebp), %eax movl %eax, (%esp) call _goo movl 8(%ebp), %eax leave ret

Esto está usando GCC 4.9.2 (en cygwin de 32 bits si es importante), sin optimizaciones. De hecho, GCC hizo exactamente lo que pensaste que debería hacer y usó el argumento directamente desde donde lo colocó la persona que llamó en la pila.


Las reglas para C son que los parámetros deben pasarse por valor. Un compilador convierte de un idioma (con un conjunto de reglas) a un idioma diferente (potencialmente con un conjunto de reglas completamente diferente). La única limitación es que el comportamiento sigue siendo el mismo. Las reglas del lenguaje C no se aplican al idioma de destino (por ejemplo, ensamblaje).

Lo que esto significa es que si un compilador tiene ganas de generar lenguaje ensamblador donde los parámetros se pasan por referencia y no se pasan por valor; entonces esto es perfectamente legal (mientras el comportamiento permanezca igual).

La verdadera limitación no tiene nada que ver con C en absoluto. La verdadera limitación es vincular. Para que los diferentes archivos de objeto se puedan vincular entre sí, se necesitan estándares para garantizar que todo lo que la persona que llama en un archivo de objeto espera coincide con lo que proporciona el destinatario en otro archivo de objeto. Esto es lo que se conoce como ABI. En algunos casos (por ejemplo, de 64 bits, 80x86), existen múltiples ABI diferentes para la misma arquitectura.

Incluso puedes inventar tu propio ABI que sea radicalmente diferente (e implementar tus propias herramientas que soporten tu propio ABI radicalmente diferente) y eso es perfectamente legal en lo que respecta a los estándares C; incluso si su ABI requiere "pasar por referencia" para todo (siempre que el comportamiento siga siendo el mismo).