Cette question s'adresse aux gourous C là-bas:
En C, il est possible de déclarer un pointeur comme suit:
char (* p)[10];
.. qui indique essentiellement que ce pointeur pointe vers un tableau de 10 caractères. La chose intéressante à propos de la déclaration d'un pointeur comme celui-ci est que vous obtiendrez une erreur de compilation si vous essayez d'attribuer un pointeur d'un tableau de taille différente à p. Cela vous donnera également une erreur de compilation si vous essayez d'attribuer la valeur d'un simple pointeur char à p. J'ai essayé cela avec gcc et cela semble fonctionner avec ANSI, C89 et C99.
Il me semble que déclarer un pointeur comme celui-ci serait très utile - en particulier, lors du passage d'un pointeur vers une fonction. Habituellement, les gens écriraient le prototype d'une telle fonction comme ceci:
void foo(char * p, int plen);
Si vous attendiez un tampon d'une taille spécifique, vous testeriez simplement la valeur de plen. Cependant, vous ne pouvez pas être assuré que la personne qui vous transmet p vous donnera vraiment plen emplacements de mémoire valides dans ce tampon. Vous devez avoir confiance que la personne qui a appelé cette fonction fait la bonne chose. D'autre part:
void foo(char (*p)[10]);
.. forcerait l'appelant à vous donner un tampon de la taille spécifiée.
Cela semble très utile mais je n'ai jamais vu un pointeur déclaré comme celui-ci dans aucun code que j'ai jamais rencontré.
Ma question est la suivante: y a-t-il une raison pour laquelle les gens ne déclarent pas de pointeurs comme celui-ci? Ne vois-je pas un écueil évident?
10
peut être remplacé par n'importe quelle variable dans la portéeRéponses:
Ce que vous dites dans votre message est absolument correct. Je dirais que chaque développeur C arrive exactement à la même découverte et à exactement la même conclusion quand (s'il) atteint un certain niveau de maîtrise du langage C.
Lorsque les spécificités de votre domaine d'application appellent un tableau de taille fixe spécifique (la taille du tableau est une constante au moment de la compilation), la seule façon appropriée de transmettre un tel tableau à une fonction consiste à utiliser un paramètre pointeur vers tableau
(en langage C ++, cela se fait également avec des références
).
Cela activera la vérification du type au niveau de la langue, ce qui garantira que le tableau de taille exactement correcte est fourni comme argument. En fait, dans de nombreux cas, les gens utilisent cette technique implicitement, sans même s'en rendre compte, en cachant le type de tableau derrière un nom typedef
Notez en outre que le code ci-dessus est invariant, le
Vector3d
type étant un tableau ou unstruct
. Vous pouvez basculer la définition deVector3d
à tout moment d'un tableau à astruct
et inversement, et vous n'aurez pas à modifier la déclaration de la fonction. Dans les deux cas, les fonctions recevront un objet agrégé "par référence" (il y a des exceptions à cela, mais dans le contexte de cette discussion, c'est vrai).Cependant, vous ne verrez pas cette méthode de passage de tableau utilisée explicitement trop souvent, simplement parce que trop de gens sont déroutés par une syntaxe plutôt alambiquée et ne sont tout simplement pas assez à l'aise avec ces fonctionnalités du langage C pour les utiliser correctement. Pour cette raison, dans la vie réelle moyenne, passer un tableau comme pointeur vers son premier élément est une approche plus populaire. Cela semble juste "plus simple".
Mais en réalité, utiliser le pointeur vers le premier élément pour le passage d'un tableau est une technique très niche, une astuce, qui sert un objectif très spécifique: son seul et unique objectif est de faciliter le passage de tableaux de taille différente (c'est-à-dire de taille d' exécution) . Si vous avez vraiment besoin de pouvoir traiter des tableaux de taille d'exécution, alors la bonne façon de passer un tel tableau est par un pointeur vers son premier élément avec la taille concrète fournie par un paramètre supplémentaire
En fait, dans de nombreux cas, il est très utile de pouvoir traiter des tableaux de taille d'exécution, ce qui contribue également à la popularité de la méthode. De nombreux développeurs C ne rencontrent tout simplement jamais (ou ne reconnaissent jamais) la nécessité de traiter un tableau de taille fixe, restant ainsi inconscients de la technique de taille fixe appropriée.
Néanmoins, si la taille du tableau est fixe, le passer en tant que pointeur vers un élément
est une erreur technique majeure, malheureusement assez répandue de nos jours. Une technique pointeur vers tableau est une bien meilleure approche dans de tels cas.
Une autre raison qui pourrait entraver l'adoption de la technique de passage de tableaux de taille fixe est la prédominance de l'approche naïve du typage des tableaux alloués dynamiquement. Par exemple, si le programme appelle des tableaux fixes de type
char[10]
(comme dans votre exemple), un développeur moyen utiliseramalloc
des tableaux tels queCe tableau ne peut pas être passé à une fonction déclarée comme
ce qui déroute le développeur moyen et lui fait abandonner la déclaration de paramètre de taille fixe sans y réfléchir davantage. En réalité cependant, la racine du problème réside dans l'
malloc
approche naïve . Lemalloc
format indiqué ci-dessus doit être réservé aux tableaux de taille d'exécution. Si le type de tableau a une taille au moment de la compilation, une meilleure façon demalloc
procéder serait la suivanteCeci, bien sûr, peut être facilement transmis à la déclaration ci-dessus
foo
et le compilateur effectuera la vérification de type appropriée. Mais encore une fois, cela est trop déroutant pour un développeur C non préparé, c'est pourquoi vous ne le verrez pas trop souvent dans le code quotidien moyen "typique".
la source
const
propriété avec cette technique. Unconst char (*p)[N]
argument ne semble pas compatible avec un pointeur verschar table[N];
Par contre, un simplechar*
ptr reste compatible avec unconst char*
argument.(*p)[i]
et non*p[i]
. Ce dernier sautera de la taille du tableau, ce qui n'est certainement pas ce que vous voulez. Au moins pour moi, apprendre cette syntaxe a causé, plutôt que prévenu, une erreur; J'aurais obtenu le code correct plus rapidement en passant simplement un float *.const
pointeur vers un tableau d'éléments mutables. Et oui, c'est complètement différent d'un pointeur vers un tableau d'éléments immuables.Je voudrais ajouter à la réponse d'AndreyT (au cas où quelqu'un tomberait sur cette page à la recherche de plus d'informations sur ce sujet):
Au fur et à mesure que je commence à jouer plus avec ces déclarations, je me rends compte qu'un handicap majeur y est associé en C (apparemment pas en C ++). Il est assez courant d'avoir une situation où vous voudriez donner à un appelant un pointeur const vers un tampon dans lequel vous avez écrit. Malheureusement, cela n'est pas possible lors de la déclaration d'un pointeur comme celui-ci en C. En d'autres termes, le standard C (6.7.3 - Paragraphe 8) est en contradiction avec quelque chose comme ceci:
Cette contrainte ne semble pas être présente en C ++, ce qui rend ce type de déclarations beaucoup plus utile. Mais dans le cas de C, il est nécessaire de revenir à une déclaration de pointeur régulière chaque fois que vous voulez un pointeur const vers le tampon de taille fixe (à moins que le tampon lui-même n'ait été déclaré const pour commencer). Vous pouvez trouver plus d'informations dans ce fil de discussion: texte du lien
C'est une contrainte sévère à mon avis et cela pourrait être l'une des principales raisons pour lesquelles les gens ne déclarent généralement pas de pointeurs comme celui-ci en C. L'autre étant le fait que la plupart des gens ne savent même pas que vous pouvez déclarer un pointeur comme celui-ci comme AndreyT a souligné.
la source
La raison évidente est que ce code ne compile pas:
La promotion par défaut d'un tableau est vers un pointeur non qualifié.
Voir également cette question , l'utilisation
foo(&p)
devrait fonctionner.la source
foo(&p)
.Je souhaite également utiliser cette syntaxe pour activer davantage de vérification de type.
Mais je conviens également que la syntaxe et le modèle mental d'utilisation des pointeurs sont plus simples et plus faciles à retenir.
Voici quelques autres obstacles que j'ai rencontrés.
L'accès à la baie nécessite l'utilisation de
(*p)[]
:Il est tentant d'utiliser à la place un pointeur vers un caractère local:
Mais cela irait en partie à l'encontre de l'objectif d'utiliser le bon type.
Il faut se rappeler d'utiliser l'opérateur address-of lors de l'affectation de l'adresse d'un tableau à un pointeur vers tableau:
L'opérateur address-of obtient l'adresse de l'ensemble du tableau dans
&a
, avec le type correct auquel l'affecterp
. Sans l'opérateur,a
est automatiquement convertie en l'adresse du premier élément du tableau, comme dans&a[0]
, qui a un type différent.Comme cette conversion automatique est déjà en cours, je suis toujours perplexe quant à la
&
nécessité. Cela est cohérent avec l'utilisation de&
sur des variables d'autres types, mais je dois me rappeler qu'un tableau est spécial et que j'ai besoin du&
pour obtenir le type d'adresse correct, même si la valeur d'adresse est la même.Une des raisons de mon problème est peut-être que j'ai appris K&R C dans les années 80, ce qui ne permettait pas encore d'utiliser l'
&
opérateur sur des tableaux entiers (bien que certains compilateurs l'ignorent ou tolèrent la syntaxe). Ce qui, au fait, peut être une autre raison pour laquelle les pointeurs vers des tableaux ont du mal à être adoptés: ils ne fonctionnent correctement que depuis ANSI C, et la&
limitation des opérateurs peut avoir été une autre raison de les juger trop maladroits.Quand
typedef
n'est pas utilisé pour créer un type pour le pointeur-vers-tableau (dans un fichier d'en-tête commun), alors un pointeur-vers-tableau global a besoin d'uneextern
déclaration plus compliquée pour le partager entre les fichiers:la source
Eh bien, simplement, C ne fait pas les choses de cette façon. Un tableau de type
T
est passé en tant que pointeur vers le premierT
du tableau, et c'est tout ce que vous obtenez.Cela permet des algorithmes sympas et élégants, tels que la boucle dans le tableau avec des expressions comme
L'inconvénient est que la gestion de la taille dépend de vous. Malheureusement, le fait de ne pas le faire consciencieusement a également conduit à des millions de bogues dans le codage C et / ou des opportunités d'exploitation malveillante.
Ce qui se rapproche de ce que vous demandez en C est de faire passer un
struct
(par valeur) ou un pointeur vers un (par référence). Tant que le même type de structure est utilisé des deux côtés de cette opération, le code qui distribue la référence et le code qui l'utilise sont en accord sur la taille des données traitées.Votre structure peut contenir toutes les données souhaitées; il peut contenir votre tableau d'une taille bien définie.
Pourtant, rien ne vous empêche, vous ou un codeur incompétent ou malveillant, d'utiliser des transtypages pour tromper le compilateur en lui faisant traiter votre structure comme une structure de taille différente. La capacité presque sans entrave de faire ce genre de chose fait partie de la conception de C.
la source
Vous pouvez déclarer un tableau de caractères de différentes manières:
Le prototype d'une fonction qui prend un tableau par valeur est:
ou par référence:
ou par syntaxe de tableau:
la source
char (*p)[10] = malloc(sizeof *p)
.Je ne recommanderais pas cette solution
car cela masque le fait que Vector3D a un type que vous devez connaître. Les programmeurs ne s'attendent généralement pas à ce que les variables du même type aient des tailles différentes. Considérer :
où sizeof a! = sizeof b
la source
sizeof(a)
pas la même chose quesizeof(b)
?Peut-être que je manque quelque chose, mais ... puisque les tableaux sont des pointeurs constants, cela signifie essentiellement qu'il est inutile de leur passer des pointeurs.
Ne pourriez-vous pas simplement utiliser
void foo(char p[10], int plen);
?la source
Sur mon compilateur (vs2008), il est traité
char (*p)[10]
comme un tableau de pointeurs de caractères, comme s'il n'y avait pas de parenthèses, même si je compile en tant que fichier C. Le compilateur prend-il en charge cette "variable"? Si tel est le cas, c'est une raison majeure de ne pas l'utiliser.la source