2012-04-17 11 views
18

considerar el siguiente código (que se produjo como resultado de this discussion):Equivalencia de p [0] y * p para los tipos de matriz incompletas

#include <stdio.h> 

void foo(int (*p)[]) {   // Argument has incomplete array type 
    printf("%d\n", (*p)[1]); 
    printf("%d\n", p[0][1]); // Line 5 
} 

int main(void) { 
    int a[] = { 5, 6, 7 }; 
    foo(&a);     // Line 10 
} 

GCC 4.3.4 complains con el mensaje de error:

prog.c: In function ‘foo’: 
prog.c:5: error: invalid use of array with unspecified bounds 

mensaje de error en el mismo GCC 4.1.2, y parece ser invariable de -std=c99, -Wall, -Wextra.

No está contento con la expresión p[0], pero está contento con *p, aunque estos deberían (en teoría) ser equivalentes. Si comento la línea 5, el código se compila y hace lo que yo esperaría (muestra 6).

Es de suponer que una de las siguientes situaciones:

  1. Mi comprensión de la norma (s) C es incorrecta, y estas expresiones no son equivalente.
  2. GCC tiene un error.

Pondría mi dinero en (1).

Pregunta: ¿Alguien puede explicar este comportamiento?

Aclaración: Soy consciente de que esto se puede "resolver" especificando un tamaño de matriz en la definición de la función. Eso no es lo que me interesa


Para los puntos de "prima":. Alguien puede confirmar que MSVC 2010 está en un error cuando se rechaza la línea 10 con el siguiente mensaje?

1><snip>\prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]' 
+0

¿Desde cuándo es (* p) [] una expresión de tipo * array *, completa o no? –

+0

@JohnBode: Bueno, si tuviéramos 'int (* p) [10]', entonces '(* p)' sería del tipo de matriz. Dado que no tenemos tamaño, creo que eso lo hace incompleto. –

Respuesta

12

Sección 6.5.2.1 del n1570, subíndices de matriz:

Restricciones

Una de las expresiones tendrán el tipo '' puntero a completar tipo de objeto '', el otro expresión tendrá un tipo entero, y el resultado tiene el tipo '' tipo ''.

Así que la norma prohíbe la expresión p[0] si p es un puntero a un tipo incompleto. No existe tal restricción para el operador de direccionamiento indirecto *.

En versiones anteriores/borradores del estándar, sin embargo, (n1256 y C99), la palabra "completa" no aparece en ese párrafo. Al no participar de ninguna manera en el procedimiento estándar, solo puedo adivinar si se trata de un cambio radical o la corrección de una omisión. El comportamiento del compilador sugiere lo último.Esto se ve reforzado por el hecho de que p[i] es por el estándar idéntico a *(p + i) y la última expresión no tiene sentido para un puntero a un tipo incompleto, por lo que p[0] funciona si p es un puntero a un tipo incompleto, un especial explícito caso sería necesario.

+1

¿Qué versión de la norma es esa? Mi borrador C99 no tiene la palabra "completo". Además, ¿cuál es la redacción equivalente en su versión para el operador de indirección? –

+0

Es n1570. Déjame echar un vistazo a las versiones anteriores. –

+0

Interesante. +1 mientras tanto, pero si puede arrojar alguna luz sobre si este es un cambio radical en el estándar (poco probable), o solo una omisión en versiones anteriores, entonces definitivamente aceptaré esta respuesta cuando me despierte. mañana! –

4

Mi C es un poco oxidado, pero mi lectura es que cuando se tiene una int (*p)[] esto:

(*p)[n] 

dice que "eliminar la referencia p para obtener una matriz de enteros, a continuación, tomar el enésimo". Lo cual parece naturalmente estar bien definido. Considerando que la presente:

p[n][m] 

diga "tomar la matriz de orden n en p, entonces toma el elemento MTH de esa matriz". Lo cual no parece bien definido en absoluto; tienes que saber qué tan grandes son las matrices para encontrar dónde comienza la enésima.

Este podía trabajo para el caso especial específica donde n = 0, porque la matriz 0 se encuentra fácil de encontrar sin importar lo grande que las matrices son. Simplemente descubrió que GCC no está reconociendo este caso especial. No conozco la especificación del idioma en detalle, así que no sé si es un "error" o no, pero mi gusto personal en el diseño del lenguaje es que p[n][m] debería funcionar o no, no debería funcionar cuando n es estáticamente sabido que es 0 y no de otra manera.

¿Es *p <===> p[0] realmente una regla definitiva de la especificación del lenguaje, o simplemente una observación? No creo que piense de desreferenciación e indización por cero como la misma operación cuando estoy programando.

+0

La especificación del lenguaje indica que 'p [0] === * (p + 0)'. Si se conoce el tamaño del tipo apuntado, eso es por supuesto lo mismo que '* p', pero con un tamaño desconocido, necesitaría una carcasa especial para que 0 funcione. –

+0

Sí, cuando se mira lo que está pasando en la capucha, tiene perfecto sentido que difieran. Creo que mi intuición fue borrosa por el hecho de que en * la mayoría * de los casos, tratamos a '* p' y' p [0] 'como si fueran lógicamente equivalentes. –

4

Para su pregunta de "puntos de bonificación" (probablemente debería haber hecho esta pregunta por separado), MSVC10 tiene un error. Tenga en cuenta que MSVC solo implementa C89, por lo que he usado ese estándar.

Para la llamada de función, C89 §3.3.2.2 nos dice:

Cada argumento tendrá un tipo tal que su valor puede ser asignado a un objeto con la versión no calificado del tipo de su parámetro correspondiente.

Las limitaciones para la asignación están en C89 §3.3.16:

una de las siguientes asirá: ... ambos operandos son punteros a versiones cualificados o no de tipos compatibles, y la tipo apuntado por la izquierda tiene todos los calificadores del tipo apuntado a por la derecha;

Así podemos asignar dos punteros (y por lo tanto llamar a una función con un parámetro de puntero utilizando un argumento de un puntero) si los dos punteros apuntan a tipos compatibles.

La compatibilidad de los diversos tipos de matriz se define en C89 §3.5.4.2:

Durante dos tipos de matriz para que sean compatibles, tanto tendrá compatibles tipos de elemento, y si ambos especificadores de tamaño están presentes, deberá tener el mismo valor.

Para los dos tipos de matriz int [] y int [3] esta condición se cumple claramente. Por lo tanto, la llamada a la función es legal.

+0

+1 para esto. Y estoy de acuerdo, esto debería haber sido una pregunta separada. –

0
void foo(int (*p)[]) 
{ 
    printf("%d\n", (*p)[1]); 
    printf("%d\n", p[0][1]); // Line 5 
} 

Aquí, p es un puntero a un array de un número indeterminado de int s. *p accede a esa matriz, por lo que (*p)[1] es el segundo elemento de la matriz.

p[n] agrega py n veces el tamaño de la matriz apuntada, que se desconoce. Incluso antes de considerar el [1], está roto. Es cierto que cero veces cualquier cosa sigue siendo 0, pero obviamente el compilador verifica la validez de todos los términos sin cortocircuitos tan pronto como ve cero. Entonces ...

No está contento con la expresión p [0], pero está contento con * p, aunque estos deberían ser (en teoría) equivalentes.

Como se ha explicado, son claramente no es equivalente ... pensar en p[0] como p + 0 * sizeof *p y es obvio por qué ....

Para los puntos de "prima": Alguien puede confirmar que MSVC 2010 es por error cuando rechaza la línea 10 con el siguiente mensaje? 1> \ prog.c (10): C4048 advertencia: diferentes subíndices de matriz: 'int () []' y 'int () [3]'

Visual C++ (y otros compiladores) son libre de advertir acerca de cosas que piensan que no son buenas prácticas, cosas que se ha descubierto empíricamente que a menudo son erróneas, o cosas que los escritores del compilador simplemente tenían una desconfianza irracional, incluso si son completamente legales respecto del Estándar ... Los ejemplos que pueden ser familiares incluyen "comparar firmado y sin signo" y "asignación dentro de un condicional (sugiera rodear con paréntesis adicionales)"

Cuestiones relacionadas