Veuillez consulter le code suivant. Il essaie de passer un tableau en tant que char**
à une fonction:
#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;
}
Le fait que je ne puisse le faire que compiler en faisant explicitement fond &test2
sur des char**
indices déjà que ce code est incorrect.
Pourtant, je me demande ce qui ne va pas exactement. Je peux passer un pointeur vers un pointeur vers un tableau alloué dynamiquement mais je ne peux pas passer un pointeur vers un pointeur pour un tableau sur la pile. Bien sûr, je peux facilement contourner le problème en affectant d'abord le tableau à une variable temporaire, comme ceci:
char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);
Pourtant, quelqu'un peut me expliquer pourquoi il ne fonctionne pas coulé char[256]
à char**
directement?
char (*)[256]
àchar**
?char**
. Sans cette distribution, il ne se compile pas.test
est un tableau, pas un pointeur, et&test
est un pointeur vers le tableau. Ce n'est pas un pointeur vers un pointeur.Vous avez peut-être été informé qu'un tableau est un pointeur, mais cela est incorrect. Le nom d'un tableau est un nom de l'objet entier - tous les éléments. Ce n'est pas un pointeur sur le premier élément. Dans la plupart des expressions, un tableau est automatiquement converti en pointeur vers son premier élément. C'est une commodité qui est souvent utile. Mais il y a trois exceptions à cette règle:
sizeof
.&
.Dans
&test
, le tableau est l'opérande de&
, donc la conversion automatique ne se produit pas. Le résultat de&test
est un pointeur vers un tableau de 256char
, qui a un typechar (*)[256]
, nonchar **
.Pour obtenir un pointeur vers un pointeur vers
char
detest
, vous devez d'abord créer un pointeur verschar
. Par exemple:Une autre façon de penser à cela est de réaliser que le
test
nom de l'objet entier - le tableau entier de 256char
. Il ne nomme pas de pointeur, donc, dans&test
, il n'y a pas de pointeur dont l'adresse peut être prise, donc cela ne peut pas produire unchar **
. Pour créer unchar **
, vous devez d'abord avoir unchar *
.la source
_Alignof
opérateur mentionné en plus desizeof
et&
. Je me demande pourquoi ils l'ont enlevé ..._Alignof
accepte uniquement un nom de type comme opérande et n'a jamais accepté un tableau ou tout autre objet comme opérande. (Je ne sais pas pourquoi; il semble que cela puisse être syntaxiquement et grammaticalementsizeof
, mais ce n'est pas le cas.)Le type de
test2
estchar *
. Ainsi, le type de&test2
serachar **
compatible avec le type de paramètrex
deprintchar()
.Le type de
test
estchar [256]
. Ainsi, le type de&test
serachar (*)[256]
qui n'est pas compatible avec le type de paramètrex
deprintchar()
.Permettez-moi de vous montrer la différence en termes d'adresses de
test
ettest2
.Production:
Point à noter ici:
La sortie (adresse mémoire) de
test2
et&test2[0]
est numériquement identique et leur type est également le mêmechar *
.Mais les
test2
et&test2
sont des adresses différentes et leur type est également différent.Le type de
test2
estchar *
.Le type de
&test2
estchar **
.La sortie (adresse mémoire) de
test
,&test
et&test[0]
est numériquement identique , mais leur type est différent .Le type de
test
estchar [256]
.Le type de
&test
estchar (*) [256]
.Le type de
&test[0]
estchar *
.Comme le montre la sortie
&test
est la même que&test[0]
.Par conséquent, vous obtenez un défaut de segmentation.
la source
Vous ne pouvez pas accéder à un pointeur vers un pointeur car ce
&test
n'est pas un pointeur, c'est un tableau.Si vous prenez l'adresse d'un tableau, convertissez le tableau et l'adresse du tableau en
(void *)
, et comparez-les, ils seront (sauf pédanterie de pointeur possible) équivalents.Ce que vous faites vraiment est similaire à cela (encore une fois, sauf aliasing strict):
ce qui est manifestement faux.
la source
Votre code attend l'argument
x
deprintchar
pointer vers la mémoire qui contient un(char *)
.Dans le premier appel, il pointe vers le stockage utilisé
test2
et est donc bien une valeur qui pointe vers a(char *)
, cette dernière pointant vers la mémoire allouée.Dans le deuxième appel, cependant, il n'y a aucun endroit où une telle
(char *)
valeur pourrait être stockée et il est donc impossible de pointer vers une telle mémoire. Le casting que(char **)
vous avez ajouté aurait supprimé une erreur de compilation (sur la conversion(char *)
en(char **)
), mais cela ne ferait pas apparaître le stockage de nulle part pour contenir un(char *)
initialisé pointant vers les premiers caractères du test. La conversion du pointeur en C ne modifie pas la valeur réelle du pointeur.Pour obtenir ce que vous voulez, vous devez le faire explicitement:
Je suppose que votre exemple est une distillation d'un morceau de code beaucoup plus grand; à titre d'exemple, vous souhaitez peut-être
printchar
incrémenter la(char *)
valeur vers laquelle lax
valeur transmise pointe afin que lors du prochain appel, le caractère suivant soit imprimé. Si ce n'est pas le cas, pourquoi ne pas simplement passer un(char *)
pointage vers le caractère à imprimer, ou même simplement passer le caractère lui-même?la source
char **
. Les variables / objets de tableau sont simplement le tableau, l'adresse étant implicite, non stockée nulle part. Aucun niveau supplémentaire d'indirection pour y accéder, contrairement à une variable de pointeur qui pointe vers un autre stockage.Apparemment, prendre l'adresse de
test
équivaut à prendre l'adresse detest[0]
:Compilez cela et exécutez:
Ainsi, la cause ultime de l'erreur de segmentation est que ce programme essaiera de déréférencer l'adresse absolue
0x42
(également connue sous le nom de'B'
), que votre programme n'a pas la permission de lire.Bien qu'avec un compilateur / machine différent, les adresses seront différentes: essayez-le en ligne! , mais vous l'obtiendrez toujours, pour une raison quelconque:
Mais l'adresse qui cause le défaut de segmentation peut très bien être différente:
la source
test
n'est pas la même chose que prendre l'adresse detest[0]
. Le premier a le typechar (*)[256]
et le second le typechar *
. Ils ne sont pas compatibles et la norme C leur permet d'avoir des représentations différentes.%p
, il doit être converti envoid *
(à nouveau pour des raisons de compatibilité et de représentation).printchar(&test);
peut se bloquer pour vous, mais le comportement n'est pas défini par la norme C et les gens peuvent observer d'autres comportements dans d'autres circonstances.&test == &test[0]
viole les contraintes de C 2018 6.5.9 2 car les types ne sont pas compatibles. La norme C nécessite une implémentation pour diagnostiquer cette violation et le comportement résultant n'est pas défini par la norme C. Cela signifie que votre compilateur peut produire du code les évaluant comme égaux, mais pas un autre compilateur.La représentation de
char [256]
dépend de l'implémentation. Ce ne doit pas être le même quechar *
.Coulée
&test
du typechar (*)[256]
dechar **
rendements comportement non défini.Avec certains compilateurs, il peut faire ce que vous attendez, et pas d'autres.
ÉDITER:
Après avoir testé avec gcc 9.2.1, il semble qu'en
printchar((char**)&test)
faittest
, la valeur soit convertie enchar**
. C'est comme si l'instruction étaitprintchar((char**)test)
. Dans laprintchar
fonction,x
est un pointeur sur le premier caractère du test de tableau, pas un double pointeur sur le premier caractère. Une double dé-référencex
entraîne un défaut de segmentation car les 8 premiers octets du tableau ne correspondent pas à une adresse valide.J'obtiens exactement le même comportement et le même résultat lors de la compilation du programme avec clang 9.0.0-2.
Cela peut être considéré comme un bogue du compilateur, ou le résultat d'un comportement non défini dont le résultat peut être spécifique au compilateur.
Un autre comportement inattendu est que le code
La sortie est
Le comportement étrange est cela
x
et*x
a la même valeur.C'est un truc de compilateur. Je doute que cela soit défini par la langue.
la source
char (*)[256]
dépend de la mise en œuvre? La représentation dechar [256]
n'est pas pertinente dans cette question - c'est juste un tas de bits. Mais, même si vous voulez dire que la représentation d'un pointeur sur un tableau est différente de la représentation d'un pointeur sur un pointeur, cela manque également le point. Même s'ils ont les mêmes représentations, le code de l'OP ne fonctionnerait pas, car le pointeur vers un pointeur peut être déréférencé deux fois, comme cela est fait dansprintchar
, mais le pointeur vers un tableau ne peut pas, quelle que soit la représentation.char (*)[256]
àchar **
est acceptée par le compilateur, mais ne donne pas le résultat attendu car achar [256]
n'est pas identique à achar *
. J'ai supposé que l'encodage est différent, sinon cela donnerait le résultat attendu.char **
, le comportement n'est pas défini, et que, sinon, si le résultat est reconverti enchar (*)[256]
, il se compare au pointeur d'origine. Par «résultat attendu», vous pourriez signifier que, s'il(char **) &test
est encore converti en achar *
, il se compare à&test[0]
. Ce n'est pas un résultat improbable dans les implémentations qui utilisent un espace d'adressage plat, mais ce n'est pas uniquement une question de représentation.char **
estchar *
), le comportement n'est pas défini. Sinon, la conversion est définie, bien que la valeur ne soit que partiellement définie, par mon commentaire ci-dessus.char (*x)[256]
n'est pas la même chose quechar **x
. La raisonx
et l'*x
impression de la même valeur de pointeurx
est simplement un pointeur vers le tableau. Votre*x
est le tableau , et son utilisation dans un contexte de pointeur se désintègre à l'adresse du tableau . Pas de bogue de compilateur là-bas (ou dans quoi(char **)&test
), juste un peu de gymnastique mentale nécessaire pour comprendre ce qui se passe avec les types. (cdecl l'explique comme "déclarer x comme pointeur vers le tableau 256 de char"). Même utiliserchar*
pour accéder à la représentation d'objet d'unchar**
n'est pas UB; il peut tout alias.