Por favor, eche un vistazo al siguiente código. Intenta pasar una matriz como a char**
a una función:
#include <stdio.h>
#include <stdlib.h>
static void printchar(char **x)
{
printf("Test: %c\n", (*x)[0]);
}
int main(int argc, char *argv[])
{
char test[256];
char *test2 = malloc(256);
test[0] = 'B';
test2[0] = 'A';
printchar(&test2); // works
printchar((char **) &test); // crashes because *x in printchar() has an invalid pointer
free(test2);
return 0;
}
El hecho de que sólo puedo que se compile por colada de manera explícita &test2
a char**
que ya da a entender que este código es incorrecto.
Aún así, me pregunto qué tiene de malo exactamente. Puedo pasar un puntero a un puntero a una matriz asignada dinámicamente, pero no puedo pasar un puntero a un puntero para una matriz en la pila. Por supuesto, puedo solucionar fácilmente el problema asignando primero la matriz a una variable temporal, así:
char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);
Aún así, puede que alguien me explique por qué no funciona para echar char[256]
a char**
directamente?
char (*)[256]
achar**
?char**
. Sin ese elenco, no se compila.test
es una matriz, no un puntero, y&test
es un puntero a la matriz. No es un puntero a un puntero.Es posible que le hayan dicho que una matriz es un puntero, pero esto es incorrecto. El nombre de una matriz es un nombre de todo el objeto, todos los elementos. No es un puntero al primer elemento. En la mayoría de las expresiones, una matriz se convierte automáticamente en un puntero a su primer elemento. Esa es una conveniencia que a menudo es útil. Pero hay tres excepciones a esta regla:
sizeof
.&
.En
&test
, la matriz es el operando de&
, por lo que no se produce la conversión automática. El resultado de&test
es un puntero a una matriz de 256char
, que tiene tipochar (*)[256]
, nochar **
.Para obtener un puntero a un puntero
char
desdetest
, primero debe hacer un puntero achar
. Por ejemplo:Otra forma de pensar en esto es darse cuenta de que
test
nombra todo el objeto, la matriz completa de 256char
. No nombra un puntero, por lo tanto,&test
no hay ningún puntero cuya dirección se pueda tomar, por lo que esto no puede producir unchar **
. Para crear unchar **
, primero debe tener unchar *
.fuente
_Alignof
mencionó el operador además desizeof
y&
. Me pregunto por qué lo quitaron ..._Alignof
solo acepta un nombre de tipo como operando y nunca aceptó una matriz o cualquier otro objeto como operando. (No sé por qué; parece sintáctica y gramaticalmente que podría sersizeof
, pero no lo es.)El tipo de
test2
eschar *
. Entonces, el tipo de&test2
seráchar **
compatible con el tipo de parámetrox
deprintchar()
.El tipo de
test
eschar [256]
. Entonces, el tipo de&test
será elchar (*)[256]
que no sea compatible con el tipo de parámetrox
deprintchar()
.Déjame mostrarte la diferencia en términos de direcciones de
test
ytest2
.Salida:
Punto a tener en cuenta aquí:
La salida (dirección de memoria) de
test2
y&test2[0]
es numéricamente igual y su tipo también es el mismo que eschar *
.Pero las direcciones
test2
y&test2
son diferentes y su tipo también es diferente.El tipo de
test2
eschar *
.El tipo de
&test2
eschar **
.La salida (dirección de memoria) de
test
,&test
y&test[0]
es numéricamente igual pero su tipo es diferente .El tipo de
test
eschar [256]
.El tipo de
&test
eschar (*) [256]
.El tipo de
&test[0]
eschar *
.Como muestra la salida
&test
es igual a&test[0]
.Por lo tanto, obtiene un fallo de segmentación.
fuente
No puede acceder a un puntero a un puntero porque
&test
no es un puntero, es una matriz.Si toma la dirección de una matriz, eche la matriz y la dirección de la matriz
(void *)
, y compárelos, serán equivalentes (salvo posible pedantería de puntero).Lo que realmente está haciendo es similar a esto (de nuevo, salvo alias estricto):
lo cual obviamente está mal.
fuente
Su código espera que el argumento
x
deprintchar
que el punto de memoria que contiene una(char *)
.En la primera llamada, apunta al almacenamiento utilizado
test2
y, por lo tanto, es un valor que apunta a a(char *)
, este último apunta a la memoria asignada.En la segunda llamada, sin embargo, no hay lugar donde se
(char *)
pueda almacenar dicho valor y, por lo tanto, es imposible señalar dicha memoria. El reparto(char **)
que agregó habría eliminado un error de compilación (sobre la conversión(char *)
a(char **)
), pero no haría que el almacenamiento apareciera de la nada para contener un(char *)
inicializado para señalar los primeros caracteres de la prueba. La conversión del puntero en C no cambia el valor real del puntero.Para obtener lo que desea, debe hacerlo explícitamente:
Supongo que su ejemplo es una destilación de un fragmento de código mucho más grande; Como ejemplo, tal vez desee
printchar
incrementar el(char *)
valor al quex
apunta el valor pasado para que en la próxima llamada se imprima el siguiente carácter. Si ese no es el caso, ¿por qué no pasa simplemente una(char *)
señal al personaje que se va a imprimir, o incluso simplemente pasa el personaje en sí?fuente
char **
. Las variables / objetos de matriz simplemente son la matriz, con la dirección implícita, no almacenada en ningún lugar. No hay un nivel adicional de indirección para acceder a ellos, a diferencia de una variable de puntero que apunta a otro almacenamiento.Aparentemente, tomar la dirección de
test
es lo mismo que tomar la dirección detest[0]
:Compila eso y ejecuta:
Entonces, la causa principal de la falla de segmentación es que este programa intentará desreferenciar la dirección absoluta
0x42
(también conocida como'B'
), que su programa no tiene permiso para leer.Aunque con un compilador / máquina diferente, las direcciones serán diferentes: ¡ Pruébelo en línea! , pero aún así obtendrá esto, por alguna razón:
Pero la dirección que causa la falla de segmentación puede ser muy diferente:
fuente
test
no es lo mismo que tomar la dirección detest[0]
. El primero tiene tipochar (*)[256]
, y el segundo tiene tipochar *
. No son compatibles, y el estándar C les permite tener representaciones diferentes.%p
, debe convertirse avoid *
(nuevamente por razones de compatibilidad y representación).printchar(&test);
puede fallar para usted, pero el comportamiento no está definido por el estándar C y las personas pueden observar otros comportamientos en otras circunstancias.&test == &test[0]
viola las restricciones en C 2018 6.5.9 2 porque los tipos no son compatibles. El estándar C requiere una implementación para diagnosticar esta violación, y el comportamiento resultante no está definido por el estándar C. Eso significa que su compilador podría producir código evaluándolos para que sean iguales, pero otro compilador podría no serlo.La representación de
char [256]
depende de la implementación. No debe ser lo mismo quechar *
.Casting
&test
de tipochar (*)[256]
parachar **
producir un comportamiento indefinido.Con algunos compiladores, puede hacer lo que espera, y en otros no.
EDITAR:
Después de probar con gcc 9.2.1, parece que
printchar((char**)&test)
de hecho pasatest
como valor emitido achar**
. Es como si la instrucción fueraprintchar((char**)test)
. En laprintchar
función,x
es un puntero al primer carácter de la prueba de matriz, no un puntero doble al primer carácter. Un resultado de doblex
desreferencia en una falla de segmentación porque los 8 primeros bytes de la matriz no corresponden a una dirección válida.Obtengo exactamente el mismo comportamiento y resultado al compilar el programa con clang 9.0.0-2.
Esto puede considerarse como un error del compilador o el resultado de un comportamiento indefinido cuyo resultado podría ser específico del compilador.
Otro comportamiento inesperado es que el código
La salida es
El comportamiento extraño es que
x
y*x
tiene el mismo valor.Esto es una cosa del compilador. Dudo que esto esté definido por el lenguaje.
fuente
char (*)[256]
depende de la implementación? La representación dechar [256]
no es relevante en esta pregunta, es solo un montón de bits. Pero, incluso si quiere decir que la representación de un puntero a una matriz es diferente de la representación de un puntero a un puntero, eso también pierde el punto. Incluso si tienen las mismas representaciones, el código del OP no funcionaría, porque el puntero a un puntero se puede desreferenciar dos veces, como se hace enprintchar
, pero el puntero a una matriz no puede, independientemente de la representación.char (*)[256]
achar **
, pero no produce el resultado esperado porque achar [256]
no es lo mismo que achar *
. Asumí, la codificación es diferente, de lo contrario produciría el resultado esperado.char **
, el comportamiento no está definido, y que, de lo contrario, si el resultado se convierte de nuevochar (*)[256]
, se compara igual al puntero original. Por "resultado esperado", puede querer decir que, si(char **) &test
se convierte más a achar *
, se compara igual a&test[0]
. Ese no es un resultado improbable en implementaciones que usan un espacio de direcciones plano, pero no es puramente una cuestión de representación.char **
eschar *
), entonces el comportamiento es indefinido. De lo contrario, la conversión está definida, aunque el valor solo está parcialmente definido, según mi comentario anterior.char (*x)[256]
No es lo mismo quechar **x
. La razónx
e*x
imprimir el mismo valor de puntero es quex
es simplemente un puntero a la matriz. Tu*x
es la matriz , y usarla en un contexto de puntero se desintegra a la dirección de la matriz . No hay errores de compilación allí (o en qué lo(char **)&test
hace), solo se requiere un poco de gimnasia mental para darse cuenta de que está sucediendo con los tipos. (cdecl lo explica como "declarar x como puntero a la matriz 256 de char"). Incluso usarchar*
para acceder a la representación de objetos de achar**
no es UB; puede alias cualquier cosa.