¿Por qué el argumento principal de C / C ++ se declara como "char * argv []" en lugar de solo "char * argv"?

21

¿Por qué se argvdeclara como "un puntero al puntero al primer índice de la matriz", en lugar de ser simplemente "un puntero al primer índice de la matriz" ( char* argv)?

¿Por qué se requiere la noción de "puntero a puntero" aquí?

un usuario
fuente
44
"un puntero a puntero al primer índice de la matriz" - Esa no es una descripción correcta de char* argv[]o char**. Eso es un puntero a un puntero a un personaje; específicamente el puntero externo apunta al primer puntero en una matriz, y los punteros internos apuntan a los primeros caracteres de cadenas terminadas en nulo. No hay índices involucrados aquí.
Sebastian Redl
12
¿Cómo obtendrías el segundo argumento si solo fuera char * argv?
gnasher729
15
Su vida será más fácil cuando coloque el espacio en el lugar correcto. char* argv[]pone el espacio en el lugar equivocado. Digamos char *argv[], y ahora está claro que esto significa "la expresión *argv[n]es una variable de tipo char". No se deje atrapar por tratar de averiguar qué es un puntero y qué es un puntero a un puntero, y así sucesivamente. La declaración le dice qué operaciones puede realizar en esta cosa.
Eric Lippert
1
Compárese mentalmente char * argv[]con la construcción similar de C ++ std::string argv[], y podría ser más fácil de analizar. ... ¡Simplemente no empieces a escribirlo de esa manera!
Justin Time 2 Restablece a Mónica el
2
@EricLippert tenga en cuenta que la pregunta también incluye C ++, y allí puede tener, por ejemplo, char &func(int);lo que no hace que &func(5)tenga tipo char.
Ruslan

Respuestas:

59

Argv es básicamente así:

ingrese la descripción de la imagen aquí

A la izquierda está el argumento en sí mismo: lo que en realidad se pasa como argumento a main. Que contiene la dirección de una matriz de punteros. Cada uno de esos puntos apunta a algún lugar en la memoria que contiene el texto del argumento correspondiente que se pasó en la línea de comando. Luego, al final de esa matriz, se garantiza que habrá un puntero nulo.

Tenga en cuenta que el almacenamiento real de los argumentos individuales al menos se puede asignar por separado, por lo que sus direcciones en la memoria pueden organizarse de manera bastante aleatoria (pero dependiendo de cómo se escriban las cosas, también podrían estar en un solo bloque contiguo de memoria: simplemente no lo sabes y no debería importarte).

Jerry Coffin
fuente
52
¡Cualquier motor de diseño que haya dibujado ese diagrama para usted tiene un error en su algoritmo de minimización de cruces!
Eric Lippert
43
@EricLippert Podría ser intencional enfatizar que los pointees podrían no ser contiguos ni estar en orden.
jamesdlin
3
Yo diría que es intencional
Michael
24
Ciertamente fue intencional, y supongo que Eric probablemente se dio cuenta de eso, pero (correctamente, en mi opinión) pensó que el comentario era divertido de todos modos.
Jerry Coffin
2
@JerryCoffin, también se podría señalar que, incluso si los argumentos reales fueran contiguos en la memoria, pueden tener longitudes arbitrarias, por lo que uno aún necesitaría punteros distintos para que cada uno de ellos pueda acceder argv[i]sin escanear todos los anteriores.
ilkkachu
22

Porque eso es lo que proporciona el sistema operativo :-)

Su pregunta es un poco un problema de inversión de huevo / gallina. El problema no es elegir lo que quieres en C ++, sino cómo dices en C ++ lo que te está dando el sistema operativo.

Unix pasa una serie de "cadenas", cada cadena es un argumento de comando. En C / C ++, una cadena es un "char *", por lo que una matriz de cadenas es char * argv [] o char ** argv, según el gusto.

transeúnte
fuente
13
No, es exactamente "el problema para elegir lo que quieres en C ++". Windows, por ejemplo, proporciona la línea de comandos como una sola cadena y, sin embargo, los programas C / C ++ aún reciben su argvmatriz: el tiempo de ejecución se encarga de tokenizar la línea de comando y construir la argvmatriz al inicio.
Joker_vD
14
@Joker_vD Creo que de una manera retorcida se trata de lo que te ofrece el sistema operativo. Específicamente: supongo que C ++ lo hizo de esta manera porque C lo hizo de esta manera, y C lo hizo de esta manera porque en ese momento C y Unix estaban tan inextricablemente vinculados y Unix lo hizo de esta manera.
Daniel Wagner
1
@DanielWagner: Sí, esto es de la herencia de Unix de C. En Unix / Linux, un mínimo _startque llama mainsolo necesita pasar mainun puntero a la argvmatriz existente en la memoria; Ya está en el formato correcto. El núcleo lo copia del argumento argv a la execve(const char *filename, char *const argv[], char *const envp[])llamada al sistema que se realizó para iniciar un nuevo ejecutable. (En Linux, argv [] (la matriz en sí) y argc están en la pila en la entrada del proceso. Supongo que la mayoría de los Unix son iguales, porque ese es un buen lugar para ello.)
Peter Cordes
8
Pero el punto de Joker aquí es que los estándares C / C ++ lo dejan a la implementación de donde provienen los argumentos; no tienen que ser directamente desde el sistema operativo. En un sistema operativo que pasa una cadena plana, una buena implementación de C ++ debe incluir la tokenización, en lugar de establecer argc=2y pasar toda la cadena plana. (Seguir la letra del estándar no es suficiente para ser útil ; intencionalmente deja mucho espacio para las opciones de implementación). Aunque algunos programas de Windows querrán tratar las citas especialmente, por lo que las implementaciones reales proporcionan una forma de obtener la cadena plana, también.
Peter Cordes
1
La respuesta de Basile es más o menos esta corrección de + Joker y mis comentarios, con más detalles.
Peter Cordes
15

Primero, como una declaración de parámetro, char **argves lo mismo que char *argv[]; ambos implican un puntero a (una matriz o conjunto de uno o más posibles) puntero (s) a cadenas.

A continuación, si solo tiene "puntero a char", por ejemplo, solo char *, para acceder al enésimo elemento, tendrá que escanear los primeros n-1 para encontrar el inicio del enésimo elemento. (Y esto también impondría el requisito de que cada una de las cadenas se almacene contiguamente).

Con la matriz de punteros, puede indexar directamente el enésimo elemento, por lo que (aunque no es estrictamente necesario, suponiendo que las cadenas son contiguas) generalmente es mucho más conveniente.

Para ilustrar:

./program hello world

argc = 3
argv[0] --> "./program\0"
argv[1] --> "hello\0"
argv[2] --> "world\0"

Es posible que, en una matriz de caracteres proporcionada por el sistema operativo:

            "./program\0hello\0world\0"
argv[0]      ^
argv[1]                 ^
argv[2]                        ^

si argv fuera solo un "puntero a char", podría ver

       "./program\0hello\0world\0"
argv    ^

Sin embargo (aunque probablemente por el diseño del sistema operativo) no existe una garantía real de que las tres cadenas "./program", "hello" y "world" sean contiguas. Además, este tipo de "puntero único a múltiples cadenas contiguas" es una construcción de tipo de datos más inusual (para C), especialmente en comparación con la matriz de punteros a cadena.

Erik Eidt
fuente
¿Qué pasa si en lugar de, argv --> "hello\0world\0"tienes argv --> index 0 of the array(hola), al igual que una matriz normal. ¿Por qué no es factible? entonces sigues leyendo los argctiempos de la matriz . entonces pasas argv en sí y no un puntero a argv.
un usuario
@auser, eso es lo que argv -> "./program\0hello\0\world\0" es: un puntero al primer carácter (es decir, el ".") Si toma ese puntero más allá del primer \ 0, entonces usted tener un puntero a "hola \ 0" y luego a "mundo \ 0". Después de los tiempos de discusión (presionando \ 0 "), ya está. Claro, se puede hacer que funcione, y como dije, una construcción inusual.
Erik Eidt
Olvidó decir que en su ejemplo argv[4]esNULL
Basile Starynkevitch
3
Hay una garantía de que (al menos inicialmente) argv[argc] == NULL. En este caso eso es argv[3], no argv[4].
Miral
1
@Hill, sí, gracias porque estaba tratando de ser explícito sobre los terminadores de caracteres nulos (y me perdí ese).
Erik Eidt
13

Por qué C / C ++ main argv se declara como "char * argv []"

Una posible respuesta es porque el estándar C11 n1570 (en §5.1.2.2.1 Inicio del programa ) y el estándar C ++ 11 n3337 (en la función principal §3.6.1 ) requieren que para entornos alojados (pero tenga en cuenta que el estándar C menciona también §5.1.2.1 entornos independientes ) Ver también esto .

La siguiente pregunta es ¿por qué los estándares C y C ++ optaron mainpor tener esa int main(int argc, char**argv)firma? La explicación es en gran medida histórica: C se inventó con Unix , que tiene un shell que hace glob antes de hacerlo fork(que es una llamada al sistema para crear un proceso) y execve(que es la llamada al sistema para ejecutar un programa), y que execvetransmite una matriz de argumentos del programa de cadena y está relacionado con el maindel programa ejecutado. Lea más sobre la filosofía de Unix y sobre ABI s.

Y C ++ se esforzó por seguir las convenciones de C y ser compatible con él. No pudo definirmain como incompatible con las tradiciones de C.

Si diseñó un sistema operativo desde cero (todavía tiene una interfaz de línea de comandos) y un lenguaje de programación para él desde cero, podrá inventar diferentes convenciones de inicio de programas. Y otros lenguajes de programación (por ejemplo, Common Lisp u Ocaml o Go) tienen diferentes convenciones de inicio de programas.

En la práctica, maines invocado por algún código crt0 . Tenga en cuenta que, en Windows, cada programa puede realizar el globbing en el equivalente de crt0, y algunos programas de Windows pueden comenzar a través del punto de entrada WinMain no estándar . En Unix, el shell lo realiza el shell (y crt0está adaptando el ABI y el diseño inicial de la pila de llamadas que ha especificado a las convenciones de llamada de su implementación en C).

Basile Starynkevitch
fuente
12

En lugar de pensar en él como "puntero a puntero", es útil pensar en él como "matriz de cadenas", con []matriz de char*denotación y cadena de denotación. Cuando ejecuta un programa, puede pasarle uno o más argumentos de línea de comandos y estos se reflejan en los argumentos a main: argces el recuento de argumentos y le argvpermite acceder a argumentos individuales.

casablanca
fuente
2
+1 esto! En muchos lenguajes (bash, PHP, C, C ++), argv es una matriz de cadenas. De esto tienes que pensar cuando ves char **o char *[], que es lo mismo.
rexkogitans
1

En muchos casos la respuesta es "porque es un estándar". Para citar el estándar C99 :

- Si el valor de argc es mayor que cero, los miembros de la matriz argv [0] a argv [argc-1] inclusive contendrán punteros a las cadenas , que el entorno host les otorga valores definidos por la implementación antes del inicio del programa.

Por supuesto, antes de que se haya estandarizado, K&R C ya lo estaba utilizando en las primeras implementaciones de Unix, con el propósito de almacenar parámetros de línea de comandos (algo que debe tener en cuenta en Unix shell como /bin/basho /bin/shno en sistemas integrados). Para citar la primera edición de "The C Programming Language" de K&R (pág. 110) :

El primero (convencionalmente llamado argc ) es el número de argumentos de línea de comandos con los que se invocó el programa; el segundo ( argv ) es un puntero a una matriz de cadenas de caracteres que contienen los argumentos, uno por cadena.

Sergiy Kolodyazhnyy
fuente