2009-11-27 20 views
96

Esta pregunta va dirigida a la gurús C por ahí:los punteros de C: apunta a un array de tamaño fijo

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 simple puntero a p. Intenté esto con gcc y parece funcionar con ANSI, C89 y C99.

Me parece que la declaración de un puntero como este sería muy útil, especialmente cuando se pasa un puntero a una función. Por lo general, la gente escriba el prototipo de una función tal como esto:

void foo(char * p, int plen); 

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

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

... forzará a la persona que llama a que proporcione un almacenamiento intermedio del tamaño especificado.

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

Mi pregunta es: ¿Hay alguna razón por la cual las personas no declaran punteros como este? ¿No estoy viendo una trampa obvia?

+1

nota: Desde C99 la matriz no tiene que ser de tamaño fijo como sugiere el título, '10' puede ser sustituido por cualquier variable en su alcance –

Respuesta

7

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

Como Comienzo a jugar más con estas declaraciones, me doy cuenta de que hay una desventaja importante asociada con ellas en C (aparentemente no en C++). Es bastante común tener una situación en la que le gustaría dar a un llamante un puntero de const a un buffer en el que ha escrito. Desafortunadamente, esto no es posible cuando se declara un puntero como este en C.En otras palabras, el patrón 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 limitación no parece estar presente en C++, por lo que este tipo de declaraciones mucho más útil. Pero en el caso de C, es necesario recurrir a una declaración de puntero regular siempre que desee un puntero de const para el buffer de tamaño fijo (a menos que el buffer en sí mismo haya sido declarado const para comenzar). Puede encontrar más información en este hilo de correo: link text

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

+1

Eso parece ser un problema específico del compilador. Pude duplicar el uso de gcc 4.9.1, pero clang 3.4.2 fue capaz de pasar de una versión sin const a const sin problema. Leí la especificación C11 (p 9 en mi versión ... la parte que habla de que dos tipos calificados son compatibles) y acepto que parece decir que estas conversiones son ilegales. Sin embargo, sabemos en la práctica que siempre puede convertir automáticamente de char * a char const * sin previo aviso. IMO, clang es más consistente al permitir esto que gcc, aunque estoy de acuerdo con usted en que la especificación parece prohibir cualquiera de estas conversiones automáticas. –

4

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 por defecto de una matriz es un puntero sin reservas.

También vea this question, usando foo(&p) debería funcionar.

+0

de foo curso (p) no lo hará trabajo, foo está pidiendo un puntero a una matriz de 10 elementos por lo que debe pasar la dirección de su array ... –

+7

¿Cómo es esa la "razón obvia"? Obviamente, se entiende que la forma correcta de llamar a la función es 'foo (& p)'. – AnT

+2

Supongo que "obvio" es la palabra incorrecta. Quise decir "más directo". La distinción entre p y & p en este caso es bastante oscura para el programador promedio de C. Alguien que intente hacer lo que el póster sugirió escribirá lo que escribí, obtendrá un error en tiempo de compilación y se dará por vencido. –

0

Tal vez me falta algo, pero ... ya que las matrices son punteros constantes, básicamente eso significa que no tiene sentido pasarles los punteros.

¿No podría simplemente usar void foo(char p[10], int plen);?

+3

Las matrices NO son punteros constantes. Lea algunas preguntas frecuentes sobre matrices, por favor. – AnT

+2

Para lo que importa aquí (matrices unidimensionales como parámetros), el hecho es que decaen en punteros constantes. Lea las preguntas frecuentes sobre cómo ser menos pedante, por favor. – fortran

1

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

Esto permite que algunos algoritmos frescas y elegantes, como bucle a través de la matriz con expresiones como

*dst++ = *src++ 

La desventaja 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/o oportunidades de explotación malévola.

Lo que se acerca a lo que pregunta en C es pasar alrededor de 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 distribuye la referencia como el código que lo utiliza están de acuerdo con 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.

Aún así, nada le impide a usted o un codificador incompetente o malévolo utilizar moldes para engañar al compilador para que trate su estructura como de un tamaño diferente. La capacidad casi no encadenada para hacer este tipo de cosas es parte del diseño de C.

1

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* 
+1

No olvide que una matriz de tamaño fijo también se puede asignar dinámicamente como 'char (* p) [10] = malloc (sizeof * p)'. – AnT

+0

Vea aquí para una discusión más detallada entre las diferencias de char array [] y char * ptr aquí. http://stackoverflow.com/questions/1807530/difference-between-using-character-pointers-and-character-arrays/1807766#1807766 – t0mm13b

138

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

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

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

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

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

).

Esto habilitará la verificación de tipo de nivel de idioma, lo que asegurará que la matriz de tamaño exactamente correcto se suministre como argumento. De hecho, en muchos casos, la gente utiliza esta técnica de manera implícita, sin 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); 

nota, además, de que el código anterior es invariante con relación a Vector3d tipo de ser una matriz o una struct. Puede cambiar la definición de Vector3d en cualquier momento desde una matriz a struct y viceversa, y no tendrá que cambiar la declaración de la 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 paso de matriz utilizado explícitamente con demasiada frecuencia, simplemente porque mucha gente se confunde por una sintaxis complicada y simplemente no son lo suficientemente cómodos con tales características del lenguaje C para usarlos correctamente. Por esta razón, en la vida real promedio, pasar una matriz como un puntero a su primer elemento es un enfoque más popular. Simplemente parece "más simple".

Pero, en realidad, utilizando el puntero al primer elemento de matriz que pasa es una técnica muy de nicho, un truco, que sirve a un propósito muy específico: su único propósito es facilitar pasar matrices de diferente tamaño (es decirtamaño de tiempo de ejecución). Si realmente necesita para ser capaz de procesar matrices de tamaño en tiempo de ejecución, a continuación, la forma correcta de pasar una matriz de este tipo 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); 

realidad En muchos casos, es muy útil poder procesar matrices de tamaño de tiempo de ejecución, lo que también contribuye a la popularidad del método. Muchos desarrolladores 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 de tamaño fijo adecuada.

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

void foo(char p[]) 

es un error importante a nivel de la técnica, que desafortunadamente es bastante generalizado en estos días. Una técnica de puntero a matriz es un enfoque mucho mejor en tales casos.

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

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

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

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

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

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

Esto, por supuesto, se puede pasar fácilmente de lo anterior declarada foo

foo(p); 

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

+4

Muy buena y elaborada respuesta. –

+1

@Johannes: Estás siendo sarcástico, ¿no? Hay muchos insultos velados en esa respuesta para llamarlo "agradable" :))) – AnT

+0

@AndreyT: Muy buena respuesta de hecho. Gracias por tomarse el tiempo para explicarlo. – figurassa

1

No recomendaría esta solución

typedef int Vector3d[3]; 

ya que oscurece el hecho de que Vector3D tiene un tipo que usted debe saber acerca de . Los programadores generalmente no esperan que las variables del mismo tipo tengan diferentes tamaños. Considere lo siguiente:!

void foo(Vector3d a) { 
    Vector3D b; 
} 

donde sizeof a = sizeof aab

+0

Él no estaba sugiriendo esto como una solución. Simplemente estaba usando esto como un ejemplo. – figurassa

-1

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

+0

-1 Incorrecto. Funciona bien en vs2008, vs2010, gcc.En particular, este ejemplo funciona bien: http://stackoverflow.com/a/19208364/2333290 – kotlomoy

1

También quiero usar esta sintaxis para habilitar más comprobación 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.

  • 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 char puntero-a-local:

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

    Pero esto frustraría parcialmente el propósito de usando el tipo correcto.

  • Uno tiene que acordarse de usar el operador de dirección en la asignación de direcciones de una matriz a una matriz de punteros-a-:

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

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

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

    Una de las razones para mi problema puede ser que aprendí K & R C en los años 80, los cuales no permiten el uso del operador & en matrices entero con todo (aunque algunos compiladores ignorados o tolerados que la sintaxis). Que, dicho sea de paso, puede ser otra razón por la que los punteros a los arreglos tienen dificultades para ser adoptados: solo funcionan correctamente ya que ANSI C y la limitación del operador & pueden haber sido otra razón para considerarlos demasiado incómodos.

  • Cuando typedef es no utilizado para crear un tipo de la matriz de punteros-a-(en un archivo de cabecera común), entonces una matriz de punteros a las necesidades mundiales de una forma más complicada declaración extern compartirlo a través de archivos :

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