por - para que sirve int argc char*argv[]
Cuando main se define sin parámetros, ¿seguirán presente argc y argv en la pila? (6)
Considera lo muy simple:
int main(void) {
return 0;
}
Lo compilé (con mingw32-gcc) y lo main.exe foo bar
como main.exe foo bar
.
Ahora, había esperado algún tipo de falla o error causado por una función principal declarada explícitamente como que no tenía parámetros de vida . La falta de errores llevó a esta pregunta, que es en realidad cuatro preguntas.
¿Por qué funciona esto? Respuesta: ¡ Porque la norma lo dice!
¿Se ignoran los parámetros de entrada o la pila está preparada con argc & argv en silencio? Respuesta: En este caso particular, la pila está preparada.
¿Cómo verifico lo anterior? Respuesta: Ver la respuesta de Rascher.
¿Es esta plataforma dependiente? Respuesta: Sí, y no.
Por qué funciona: En general, los argumentos de función se pasan en lugares específicos (registros o pila, generalmente). Una función sin argumentos nunca los verificará, por lo que su contenido es irrelevante. Esto depende de las convenciones de llamadas y nombres, pero consulte # 4.
La pila estará típicamente preparada. En plataformas donde argv es analizado por la biblioteca de tiempo de ejecución, como DOS, el compilador puede elegir no vincular en el código si nada usa argv, pero eso es complejidad que pocos consideran necesario. En otras plataformas, argv es preparado por exec () antes de que su programa se cargue.
Dependiente de la plataforma, pero en sistemas Linux, por ejemplo, puede examinar los contenidos de argv en / proc / PID / cmdline si se usan o no. Muchas plataformas también proporcionan llamadas separadas para encontrar argumentos.
Según el estándar citado por Tim Schaeffer , main no necesita aceptar los argumentos. En la mayoría de las plataformas, los argumentos en sí seguirán existiendo, pero un main () sin argumentos nunca los conocerá.
De la norma C99:
5.1.2.2.1 Inicio del programa
La función llamada al inicio del programa se llama main. La implementación no declara ningún prototipo para esta función. Se definirá con un tipo de retorno de int y sin parámetros:
int main(void) { /* ... */ }
o con dos parámetros (referidos aquí como argc y argv, aunque se puede usar cualquier nombre, ya que son locales a la función en la que están declarados):
int main (int argc, char * argv []) {/ * ... * /}
o equivalente; o de alguna otra manera definida por la implementación.
En el clásico C, puedes hacer algo similar:
void f() {}
f(5, 6);
No hay nada que le impida llamar a una función con un número diferente de parámetros, como se supone en su definición. (Los compiladores modernos, naturalmente, consideran esto como un error grave y se resistirán enérgicamente a compilar el código).
Lo mismo sucede con su función main()
. La biblioteca de tiempo de ejecución C llamará
main(argc, argv);
pero el hecho de que su función no esté preparada para recibir esos dos argumentos no tiene importancia para la persona que llama.
En la mayoría de los compiladores, __argc y __argv existen como variables globales desde la biblioteca de tiempo de ejecución. Los valores serán correctos.
En Windows, no serán correctos si el punto de entrada tiene la firma UTF-16, que también es la única forma de obtener los argumentos de comando correctos en esa plataforma. Estarán vacíos en ese caso, pero este no es su caso, y hay dos variables alternativas de widechar.
Hay algunas notas que hacer.
Básicamente, el estándar dice que lo más probable es que una función principal no tenga argumentos, o una función que tome dos argumentos, o lo que sea.
Vea por ejemplo mi respuesta a esta pregunta .
Pero tu pregunta apunta a otros hechos.
¿Por qué funciona esto? Respuesta: ¡Porque la norma lo dice!
No es correcto. Funciona por otras razones. Funciona debido a las convenciones de convocatoria.
Estas convenciones pueden ser: los argumentos se empujan en la pila, y la persona que llama es responsable de limpiar la pila. Debido a esto, en el código ASM real, la persona que llama puede ignorar totalmente lo que está en la pila. Una llamada se parece a
push value1
push value2
call function
add esp, 8
(Ejemplos de inteligencia, sólo para permanecer en la corriente principal).
Lo que la función hace con los argumentos que se empujan en la pila, es totalmente desinteresado, ¡todo seguirá funcionando bien! Y esto es cierto incluso si la convención de llamada es diferente, por ejemplo,
li $a0, value
li $a1, value
jal function
Si la función tiene en cuenta los registros $ a0 y $ a1 o no, no cambia nada.
Por lo tanto, la persona que llama puede ignorar los argumentos sin daño, cn cree que no existen o puede saber que existen, pero prefiere ignorarlos (por el contrario, sería problemático si la persona que recibe recibe los valores de la pila o los registros, mientras que la persona que llama no pasó nada).
Por eso las cosas funcionan.
Desde el punto de vista de C, si estamos en un sistema donde el código de inicio llama a main con dos argumentos (int y char **) y esperamos un valor de retorno int, el prototipo "correcto" sería
int main(int argc, char **argv) { }
Pero supongamos ahora que no usamos estos argumentos.
¿Es más correcto decir int main(void)
o int main()
(aún en el mismo sistema donde la implementación llama a main con dos argumentos y espera un valor de retorno int, como se dijo anteriormente)?
De hecho el estándar no dice lo que tenemos que hacer. El "prototipo" correcto que dice que tenemos dos argumentos sigue siendo el que se mostró anteriormente.
Pero desde un punto de vista lógico, la forma correcta de decir que hay argumentos (lo sabemos) pero no nos interesan, es
int main() { /* ... */ }
En esta respuesta , he mostrado lo que sucede si pasamos argumentos a una función declarada como int func()
y qué sucede si pasamos argumentos a una función declarada como int func(void)
.
En el segundo caso tenemos un error ya que (void)
dice explícitamente que la función no tiene argumentos.
Con main
no podemos obtener un error ya que no tenemos un prototipo real que obligue a tener argumentos, pero vale la pena señalar que gcc -std=c99 -pedantic
no da ninguna advertencia para int main()
ni para int main(void)
, y esto significaría que 1) gcc no es compatible con C99 incluso con el indicador estándar, o 2) ambas formas son compatibles con el estándar. Más probable es que sea la opción 2.
Uno cumple explícitamente con el estándar ( int main(void)
), el otro es de hecho int main(int argc, char **argv)
, pero sin decir explícitamente los argumentos, ya que no estamos interesados en ellos.
int main(void)
funciona incluso cuando existen argumentos, debido a lo que he escrito antes. Pero afirma que main no tiene argumento. Mientras que en muchos casos, si podemos escribir int main(int argc, char **argv)
, entonces es false, y en su lugar debe preferirse int main()
.
Otra cosa interesante a tener en cuenta es que si decimos que main no devuelve un valor ( void main()
) en un sistema en el que la implementación espera un valor de retorno, obtenemos una advertencia. Esto se debe a que la persona que llama espera que haga algo con él, por lo que es un "comportamiento indefinido" si no devolvemos un valor (lo que no significa poner un return
explícito en el caso main
, sino que declara main
como devolver un int). ).
En muchos códigos de inicio, he visto que el principal se llama de una de estas formas:
retval = main(_argc, _argv);
retval = main(_argc, _argv, environ);
retval = main(_argc, _argv, environ, apple); // apple specific stuff
Pero pueden existir códigos de inicio que llamen a main de manera diferente, por ejemplo, retval = main()
; en este caso, para mostrar esto, podemos usar int main(void)
, y por otro lado, usar int main(int argc, char **argv)
compilaría, pero bloquearía el programa si realmente usamos los argumentos ( ya que los valores recuperados serán basura).
¿Es esta plataforma dependiente?
La forma en que se llama main depende de la plataforma (implementación específica), según lo permiten los estándares. El "supuesto" prototipo principal es una consecuencia y, como ya se dijo, si sabemos que hay argumentos pasados pero no los usaremos, deberíamos usar int main()
, como forma abreviada para int main(int argc, char **argv)
más largo int main(int argc, char **argv)
, mientras que int main(void)
significa algo diferente: ie main no toma argumentos (eso es falso en el sistema en el que estamos pensando)
No sé la respuesta multiplataforma a su pregunta. Pero esto me dio curiosidad. ¿Asi que que hacemos? ¡Mira la pila!
Para la primera iteración:
prueba.c
int main(void) {
return 0;
}
prueba2.c
int main(int argc, char *argv[]) {
return 0;
}
Y ahora mira la salida de montaje:
$ gcc -S -o test.s test.c
$ cat test.s
.file "test.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
movl $0, %eax
popl %ebp
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
Nada emocionante aquí. Excepto por una cosa: ¡ ambos programas C tienen la misma salida de ensamblaje!
Esto básicamente tiene sentido; en realidad nunca tenemos que empujar / sacar nada de la pila para main (), ya que es lo primero en la pila de llamadas.
Entonces escribí este programa:
int main(int argc, char *argv[]) {
return argc;
}
Y su asm:
main:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
ret
Esto nos dice que "argc" se encuentra en 8(%ebp)
Así que ahora para otros dos programas de C:
int main(int argc, char *argv[]) {
__asm__("movl 8(%ebp), %eax/n/t"
"popl %ebp/n/t"
"ret");
/*return argc;*/
}
int main(void) {
__asm__("movl 8(%ebp), %eax/n/t"
"popl %ebp/n/t"
"ret");
/*return argc;*/
}
Robamos el código "return argc" de arriba y lo pegamos en el asm de estos dos programas. Cuando compilamos y ejecutamos estos, y luego invocamos echo $?
(que refleja el valor de retorno del proceso anterior) obtenemos la respuesta "correcta". Entonces, cuando ejecuto "./test abcd" entonces $?
me da "5" para ambos programas, aunque solo uno ha definido argc / argv. Esto me dice que, en mi plataforma, argc es seguro colocado en la pila. Apostaría a que una prueba similar confirmaría esto para argv.
¡Prueba esto en Windows!