¿Debo usar char ** argv o char * argv []?

125

Estoy aprendiendo C y me preguntaba cuál de estos debería usar en mi método principal. ¿Hay alguna diferencia? ¿Cuál es más común?

Kredns
fuente
2
Prefiero 'char ** argv', por lo que lo veo con más frecuencia, pero ambos son correctos y no cambiaría una declaración simplemente porque estaba escrita 'char * argv []'.
Jonathan Leffler el
+1 porque los problemas más profundos de matriz vs. puntero son importantes para entender bien.
RBerteig el
2
Muy, muy buena pregunta. Gracias.
Frank V
55
Lea la sección 6 de las preguntas frecuentes de comp.lang.c ; Es la mejor explicación de la relación entre matrices de C y punteros que he visto. Dos puntos relevantes: 1. char **argves exactamente equivalente a char *argv[]como una declaración de parámetro (y solo como una declaración de parámetro). 2. Las matrices no son punteros.
Keith Thompson

Respuestas:

160

Como recién está aprendiendo C, le recomiendo que primero intente comprender las diferencias entre matrices y punteros en lugar de las cosas comunes .

En el área de parámetros y matrices, hay algunas reglas confusas que deben quedar claras antes de continuar. Primero, lo que declaras en una lista de parámetros se trata de forma especial. Hay situaciones en las que las cosas no tienen sentido como un parámetro de función en C. Estas son

  • Funciones como parámetros
  • Matrices como parámetros

Matrices como parámetros

El segundo tal vez no está claro de inmediato. Pero queda claro cuando considera que el tamaño de una dimensión de matriz es parte del tipo en C (y una matriz cuyo tamaño de dimensión no se da tiene un tipo incompleto). Por lo tanto, si crea una función que toma una matriz por valor (recibe una copia), ¡podría hacerlo solo para un tamaño! Además, las matrices pueden hacerse grandes, y C intenta ser lo más rápido posible.

En C, por estas razones, los valores de matriz no existen. Si desea obtener el valor de una matriz, lo que obtiene es un puntero al primer elemento de esa matriz. Y aquí en realidad yace la solución. En lugar de dibujar un parámetro de matriz inválido por adelantado, un compilador de C transformará el tipo del parámetro respectivo para que sea un puntero. Recuerda esto, es muy importante. El parámetro no será una matriz, sino que será un puntero al tipo de elemento respectivo.

Ahora, si intenta pasar una matriz, lo que se pasa es un puntero al primer elemento de la matriz.

Excursión: funciones como parámetros

Para completar, y porque creo que esto lo ayudará a comprender mejor el asunto, veamos cuál es el estado de las cosas cuando intenta tener una función como parámetro. De hecho, primero no tendrá ningún sentido. ¿Cómo puede un parámetro ser una función? ¡Eh, queremos una variable en ese lugar, por supuesto! Entonces, lo que hace el compilador cuando eso sucede es, nuevamente, transformar la función en un puntero de función . Intentar pasar una función pasará un puntero a esa función respectiva. Entonces, lo siguiente es lo mismo (análogo al ejemplo de matriz):

void f(void g(void));
void f(void (*g)(void));

Tenga en cuenta que *gse necesita paréntesis . De lo contrario, especificaría una función que retorna void*, en lugar de un puntero a una función que retorna void.

Volver a las matrices

Ahora, dije al principio que las matrices pueden tener un tipo incompleto, lo que sucede si aún no se da un tamaño. Como ya pensamos que no existe un parámetro de matriz, sino que cualquier parámetro de matriz es un puntero, el tamaño de la matriz no importa. Eso significa que el compilador traducirá todo lo siguiente, y todos son lo mismo:

int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);

Por supuesto, no tiene mucho sentido poder ponerle cualquier tamaño, y simplemente se tira a la basura. Por esa razón, C99 creó un nuevo significado para esos números y permite que aparezcan otras cosas entre los corchetes:

// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory. 
int main(int c, char *argv[static 5]);

// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);

// says the same as the previous one
int main(int c, char ** const argv);

Las últimas dos líneas dicen que no podrá cambiar "argv" dentro de la función, se ha convertido en un puntero constante. Sin embargo, solo unos pocos compiladores de C admiten esas características de C99. Pero estas características dejan en claro que la "matriz" no es realmente una. Es un puntero.

Una palabra de advertencia

Tenga en cuenta que todo lo que dije anteriormente es cierto solo cuando tiene una matriz como parámetro de una función. Si trabaja con matrices locales, una matriz no será un puntero. Se comportará como un puntero, porque como se explicó anteriormente, una matriz se convertirá en un puntero cuando se lea su valor. Pero no debe confundirse con los punteros.

Un ejemplo clásico es el siguiente:

char c[10]; 
char **c = &c; // does not work.

typedef char array[10];
array *pc = &c; // *does* work.

// same without typedef. Parens needed, because [...] has 
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;
Johannes Schaub - litb
fuente
2
No tenía idea de que este existe: * argv [estática 1]
superlukas
12

Podrías usar cualquiera. Son completamente equivalentes. Ver los comentarios de litb y su respuesta .

Realmente depende de cómo quiera usarlo (y podría usarlo en cualquier caso):

// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
  while (--argc > 0)
  {
    printf("%s ", *++argv);
  }
  printf("\n");
  return 0;
}

// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i=1; i<argc; i++)
  {
    printf("%s ", argv[i]);
  }
  printf("\n");
  return 0;
}

En cuanto a lo que es más común, no importa. Cualquier programador de C experimentado que lea su código verá ambos como intercambiables (bajo las condiciones correctas). Al igual que un hablante de inglés experimentado lee "son" y "son" con la misma facilidad.

Más importante es que aprendas a leerlos y reconocer cuán similares son. Leerá más código del que escribe, y deberá sentirse igualmente cómodo con ambos.

rampion
fuente
77
char * argv [] es 100% equivalente a char ** argv cuando se usa como tipo de parámetro de una función. no hay "const" involucrado, tampoco implícitamente. Ambos son punteros a punteros a personajes. Es diferente con respecto a lo que declaras. Pero el compilador ajusta el tipo del parámetro para que sea un puntero a un puntero, aunque usted haya dicho que es una matriz. Por lo tanto, los siguientes son todos iguales: nulo f (char * p [100]); nulo f (char * p []); nulo f (char ** p);
Johannes Schaub - litb
44
En C89 (que usa la mayoría de las personas) tampoco hay forma de aprovechar el hecho de que lo declaró como una matriz (por lo que semánticamente no importa si declaró un puntero o una matriz allí; ambos se tomarán como un puntero). Comenzando con C99, puede beneficiarse de declararlo como una matriz. Lo siguiente dice: "p siempre es no nulo y apunta a una región con al menos 100 bytes": void f (char p [estática 100]); Tenga en cuenta que, en cuanto al tipo, sin embargo, p sigue siendo un puntero.
Johannes Schaub - litb
55
(en particular, & p le dará un char **, pero no un char ( ) [100], que sería el caso si p * fuera una matriz). Me sorprende que nadie haya mencionado en una respuesta todavía. Lo considero muy importante de entender.
Johannes Schaub - litb
Personalmente prefiero char**porque me recuerda que no debe tratarse como una matriz real, como hacer sizeof[arr] / sizeof[*arr].
raymai97
9

No hace la diferencia, pero lo uso char *argv[]porque muestra que es una matriz de tamaño fijo de cadenas de longitud variable (que generalmente son char *).

Zifre
fuente
4

Realmente no hace la diferencia, pero este último es más legible. Lo que se le da es una serie de punteros de caracteres, como dice la segunda versión. Sin embargo, se puede convertir implícitamente en un puntero de doble carácter como en la primera versión.

jalf
fuente
2

deberías declararlo como char *argv[], debido a todas las formas equivalentes de declararlo, que se acerca más a su significado intuitivo: una matriz de cadenas.

Andras
fuente
1

char ** → puntero al puntero de caracteres y char * argv [] significa una matriz de punteros de caracteres. Como podemos usar el puntero en lugar de una matriz, se pueden usar ambos.

Alphaneo
fuente
0

No veo ningún mérito especial de usar cualquiera de los enfoques en lugar del otro: use la convención que esté más en línea con el resto de su código.

Christoffer
fuente
-2

Si necesita un número variable o dinámico de cadenas, puede ser más fácil trabajar con char **. Sin embargo, si su número de cadena es fijo, se preferiría char * var [].

Samoz
fuente
-2

Sé que esto está desactualizado, pero si solo está aprendiendo el lenguaje de programación C y no está haciendo nada importante con él, no use las opciones de línea de comandos.

Si no está utilizando argumentos de línea de comando, no use ninguno. Simplemente declara la función principal como int main() si

  • Desea que el usuario de su programa pueda arrastrar un archivo a su programa para que pueda cambiar el resultado de su programa con él o
  • ¿Quieres manejar opciones de línea de comandos ( -help, /?, o cualquier otra cosa que va después program namede terminal o símbolo del sistema)

use el que tenga más sentido para usted. De lo contrario, solo use int main() Después de todo, si termina deseando agregar opciones de línea de comandos, puede editarlas fácilmente más adelante.

Jesse
fuente
Esto no responde la pregunta.
Lundin