Pregunta

Esta pregunta se dirige a los gurús de C:

En C, es posible declarar un puntero de la siguiente manera:

char (* p)[10];

.. que básicamente indica que este puntero apunta a una matriz de 10 caracteres. Lo bueno de declarar un puntero como este es que obtendrá un error de tiempo de compilación si intenta asignar un puntero de una matriz de diferente tamaño a p. También le dará un error de tiempo de compilación si intenta asignar el valor de un puntero de carácter simple a p. Intenté esto con gcc y parece funcionar con ANSI, C89 y C99.

Me parece que declarar un puntero como este sería muy útil, especialmente cuando se pasa un puntero a una función. Generalmente, la gente escribiría el prototipo de una función como esta:

void foo(char * p, int plen);

Si estuviera esperando un búfer de un tamaño específico, simplemente probaría el valor de plen. Sin embargo, no se puede garantizar que la persona que le pase p realmente le dará ubicaciones de memoria válidas en ese búfer. Tienes que confiar en que la persona que llamó a esta función está haciendo lo correcto. Por otro lado:

void foo(char (*p)[10]);

... obligaría a la persona que llama a darle un búfer del tamaño especificado.

Esto parece muy útil, pero nunca he visto un puntero declarado de este modo en ningún código que haya encontrado.

Mi pregunta es: ¿hay alguna razón por la que las personas no declaran punteros como este? ¿No estoy viendo algún escollo obvio?

¿Fue útil?

Solución 2

Me gustaría agregar a la respuesta de AndreyT (en caso de que alguien se tope con esta página en busca de más información sobre este tema):

Cuando empiezo a jugar más con estas declaraciones, me doy cuenta de que hay una gran desventaja asociada con ellas en C (aparentemente no en C ++). Es bastante común tener una situación en la que le gustaría darle a la persona que llama un puntero constante a un búfer en el que ha escrito. Desafortunadamente, esto no es posible cuando se declara un puntero como este en C. En otras palabras, el estándar C (6.7.3 - Párrafo 8) está en desacuerdo con algo como esto:


   int array[9];

   const int (* p2)[9] = &array;  /* Not legal unless array is const as well */

Esta restricción no parece estar presente en C ++, lo que hace que este tipo de declaraciones sea mucho más útil. Pero en el caso de C, es necesario recurrir a una declaración de puntero normal siempre que desee un puntero de const al búfer de tamaño fijo (a menos que el búfer se haya declarado constante para comenzar). Puede encontrar más información en este hilo de correo: texto del enlace

Esta es una restricción severa en mi opinión y podría ser una de las razones principales por las que las personas no suelen declarar punteros como este en C. El otro es el hecho de que la mayoría de las personas ni siquiera saben que se puede declarar un puntero así como ha señalado AndreyT.

Otros consejos

Lo que dices en tu publicación es absolutamente correcto. Diría que cada desarrollador de C llega exactamente al mismo descubrimiento y llega a la misma conclusión cuando (si) alcanzan cierto nivel de competencia con el lenguaje C.

Cuando los detalles específicos de su área de aplicación requieren una matriz de tamaño fijo específico (el tamaño de la matriz es una constante de tiempo de compilación), la única manera correcta de pasar dicha matriz a una función es mediante un puntero a matriz parámetro

void foo(char (*p)[10]);

(en lenguaje C ++ esto también se hace con referencias

void foo(char (&p)[10]);

).

Esto habilitará la verificación de tipos de nivel de idioma, lo que asegurará que la matriz del tamaño exacto correcto se proporcione como un argumento. De hecho, en muchos casos las personas utilizan esta técnica de forma implícita, sin siquiera darse cuenta, ocultando el tipo de matriz detrás de un nombre typedef

typedef int Vector3d[3];

void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);

Tenga en cuenta además que el código anterior es invariante en relación con el tipo Vector3d que es una matriz o una estructura . Puede cambiar la definición de Vector3d en cualquier momento de una matriz a struct y volver, y no tendrá que cambiar la declaración de función. En cualquier caso, las funciones recibirán un objeto agregado " por referencia " (Hay excepciones a esto, pero dentro del contexto de esta discusión esto es cierto).

Sin embargo, no verá este método de transferencia de matrices usado explícitamente con demasiada frecuencia, simplemente porque demasiadas personas se confunden con una sintaxis bastante complicada y simplemente no se sienten lo suficientemente cómodos con tales características del lenguaje C para usarlas correctamente. Por esta razón, en la vida real promedio, pasar un array como puntero a su primer elemento es un enfoque más popular. Simplemente parece " más simple " ;.

Pero en realidad, usar el puntero hacia el primer elemento para pasar la matriz es una técnica muy específica, un truco, que sirve para un propósito muy específico: su único propósito es facilitar el paso de matrices de diferentes tamaños (es decir, tamaño en tiempo de ejecución). Si realmente necesita poder procesar matrices de tamaño en tiempo de ejecución, entonces la forma correcta de pasar dicha matriz es mediante un puntero a su primer elemento con el tamaño concreto suministrado por un parámetro adicional

void foo(char p[], unsigned plen);

En realidad, en muchos casos es muy útil poder procesar matrices de tamaño en tiempo de ejecución, lo que también contribuye a la popularidad del método. Muchos desarrolladores de C simplemente nunca encuentran (o nunca reconocen) la necesidad de procesar una matriz de tamaño fijo, por lo que permanecen ajenos a la técnica adecuada de tamaño fijo.

Sin embargo, si el tamaño de la matriz es fijo, pasándolo como un puntero a un elemento

void foo(char p[])

es un error importante a nivel técnico, que lamentablemente está bastante extendido en estos días. Una técnica de puntero a matriz es un enfoque mucho mejor en estos casos.

Otra razón que podría dificultar la adopción de la técnica de paso de matrices de tamaño fijo es el predominio del enfoque ingenuo para la tipificación de matrices asignadas dinámicamente. Por ejemplo, si el programa solicita matrices fijas de tipo char [10] (como en su ejemplo), un desarrollador promedio malloc tales matrices como

char *p = malloc(10 * sizeof *p);

Esta matriz no se puede pasar a una función declarada como

char (*p)[10] = malloc(sizeof *p);

que confunde al desarrollador promedio y hace que abandone la declaración del parámetro de tamaño fijo sin tener que pensarlo más. Sin embargo, en realidad, la raíz del problema radica en el enfoque ingenuo malloc . El formato malloc que se muestra arriba se debe reservar para matrices de tamaño en tiempo de ejecución. Si el tipo de matriz tiene un tamaño de tiempo de compilación, una mejor manera de malloc se vería de la siguiente manera

foo(p);

Esto, por supuesto, se puede pasar fácilmente al foo declarado anteriormente

<*>

y el compilador realizará la comprobación de tipo adecuada. Pero, de nuevo, esto es demasiado confuso para un desarrollador de C no preparado, por lo que no lo verás con demasiada frecuencia en el " típico " código diario promedio.

La razón obvia es que este código no se compila:

extern void foo(char (*p)[10]);
void bar() {
  char p[10];
  foo(p);
}

La promoción predeterminada de una matriz es a un puntero no calificado.

También vea esta pregunta , usando foo (& amp; p) debería funcionar.

Bueno, en pocas palabras, C no hace las cosas de esa manera. Una matriz de tipo T se transmite como un puntero a la primera T en la matriz, y eso es todo lo que obtiene.

Esto permite algunos algoritmos geniales y elegantes, como recorrer la matriz con expresiones como

*dst++ = *src++

El inconveniente es que la gestión del tamaño depende de usted. Desafortunadamente, el hecho de no hacer esto concienzudamente también ha llevado a millones de errores en la codificación C y / u oportunidades para la explotación malévola.

Lo que se acerca a lo que pides en C es pasar una struct (por valor) o un puntero a uno (por referencia). Siempre que se use el mismo tipo de estructura en ambos lados de esta operación, tanto el código que entrega la referencia como el código que la usa están de acuerdo sobre el tamaño de los datos que se manejan.

Su estructura puede contener los datos que desee; podría contener su matriz de un tamaño bien definido.

Sin embargo, nada impide que usted o un codificador incompetente o malévolo utilicen conversiones para engañar al compilador para que trate su estructura como una de un tamaño diferente. La capacidad casi sin trabas para hacer este tipo de cosas es parte del diseño de C.

Puede declarar una matriz de caracteres de varias maneras:

char p[10];
char* p = (char*)malloc(10 * sizeof(char));

El prototipo de una función que toma una matriz por valor es:

void foo(char* p); //cannot modify p

o por referencia:

void foo(char** p); //can modify p, derefernce by *p[0] = 'f';

o por sintaxis de matriz:

void foo(char p[]); //same as char*

No recomendaría esta solución

typedef int Vector3d[3];

ya que oculta el hecho de que Vector3D tiene un tipo que usted debe saber sobre Los programadores no suelen esperar variables de la Mismo tipo para tener diferentes tamaños. Considera:

void foo(Vector3d a) {
   Vector3D b;
}

donde sizeof a! = sizeof b

También quiero usar esta sintaxis para habilitar más comprobaciones de tipos.

Pero también estoy de acuerdo en que la sintaxis y el modelo mental del uso de punteros es más simple y fácil de recordar.

Aquí hay algunos obstáculos más que he encontrado.

  • El acceso a la matriz requiere el uso de (* p) [] :

    void foo(char (*p)[10])
    {
        char c = (*p)[3];
        (*p)[0] = 1;
    }
    

    Es tentador utilizar un puntero a char local en su lugar:

    void foo(char (*p)[10])
    {
        char *cp = (char *)p;
        char c = cp[3];
        cp[0] = 1;
    }
    

    Pero esto anularía parcialmente el propósito de usar el tipo correcto.

  • Uno tiene que recordar usar la dirección de operador cuando se asigna una dirección de matriz a un puntero a matriz:

    char a[10];
    char (*p)[10] = &a;
    

    El operador de dirección de dirección obtiene la dirección de toda la matriz en & amp; a , con el tipo correcto para asignarla a p . Sin el operador, a se convierte automáticamente a la dirección del primer elemento de la matriz, igual que en & amp; a [0] , que tiene un tipo diferente.

    Como esta conversión automática ya se está llevando a cabo, siempre me sorprende que sea necesario & amp; . Es consistente con el uso de & amp; en variables de otros tipos, pero tengo que recordar que una matriz es especial y que necesito el & amp; para obtener la información correcta. tipo de dirección, aunque el valor de la dirección sea el mismo.

    Una razón de mi problema puede ser que aprendí K & amp; RC en los años 80, que no permitía usar el operador & amp; en arreglos completos todavía (aunque algunos compiladores ignoraron eso o lo toleraron). sintaxis). Lo que, por cierto, puede ser otra razón por la cual los punteros a las matrices tienen dificultades para ser adoptados: solo funcionan correctamente desde ANSI C, y la limitación del operador & amp; puede haber sido otra razón. considerarlos demasiado torpes.

  • Cuando typedef no es no utilizado para crear un tipo para el puntero a la matriz (en un archivo de encabezado común), entonces un puntero global a -array necesita una declaración extern más complicada para compartirla entre archivos:

    fileA:
    char (*p)[10];
    
    fileB:
    extern char (*p)[10];
    

Tal vez me esté perdiendo algo, pero ... ya que los arreglos son punteros constantes, básicamente eso significa que no tiene sentido transmitirles punteros.

¿No puedes usar void foo (char p [10], int plen); ?

En mi compilador (vs2008) trata a char (* p) [10] como una matriz de punteros de caracteres, como si no hubiera paréntesis, incluso si compilo como un archivo C. ¿Es el compilador soporte para esta " variable " ;? Si es así, esa es una razón importante para no usarlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top