Voici ce que j'ai trouvé pendant ma période d'apprentissage:
#include<iostream>
using namespace std;
int dis(char a[1])
{
int length = strlen(a);
char c = a[2];
return length;
}
int main()
{
char b[4] = "abc";
int c = dis(b);
cout << c;
return 0;
}
Donc, dans la variable int dis(char a[1])
, le [1]
semble ne rien faire et ne fonctionne pas du
tout, car je peux utiliser a[2]
. Tout comme int a[]
ou char *a
. Je sais que le nom du tableau est un pointeur et comment transmettre un tableau, donc mon puzzle ne concerne pas cette partie.
Ce que je veux savoir, c'est pourquoi les compilateurs autorisent ce comportement ( int a[1]
). Ou a-t-il d'autres significations que je ne connais pas?
typedef
type avec tableau. Ainsi, la "désintégration en pointeur" dans les types d'argument n'est pas simplement le remplacement du sucre syntaxique[]
par*
, elle passe vraiment par le système de types. Cela a des conséquences dans le monde réel pour certains types standard commeva_list
celui qui peut être défini avec un type tableau ou non-tableau.int dis(char (*a)[1])
. Ensuite, vous passez un pointeur vers un tableau:dis(&b)
. Si vous êtes prêt à utiliser des fonctionnalités C qui n'existent pas en C ++, vous pouvez également dire des choses commevoid foo(int data[static 256])
etint bar(double matrix[*][*])
, mais c'est une toute autre boîte de vers.Réponses:
C'est une bizarrerie de la syntaxe pour passer des tableaux aux fonctions.
En fait, il n'est pas possible de passer un tableau en C. Si vous écrivez une syntaxe qui semble devoir passer le tableau, ce qui se passe réellement est qu'un pointeur vers le premier élément du tableau est passé à la place.
Étant donné que le pointeur n'inclut aucune information de longueur, le contenu de votre
[]
dans la liste de paramètres formels de la fonction est en fait ignoré.La décision d'autoriser cette syntaxe a été prise dans les années 1970 et a causé beaucoup de confusion depuis ...
la source
void foo(int (*somearray)[20])
syntaxe. dans ce cas, 20 est appliqué sur les sites appelants.[]
ne sont pas ignorés dans les tableaux multidimensionnels comme indiqué dans la réponse de pat. Il était donc nécessaire d'inclure la syntaxe des tableaux. De plus, rien n'empêche le compilateur d'émettre des avertissements même sur des tableaux unidimensionnels.void foo(int (*args)[20]);
De plus, à proprement parler, C n'a pas de tableaux multidimensionnels; mais il a des tableaux dont les éléments peuvent être d'autres tableaux. Cela ne change rien.La longueur de la première dimension est ignorée, mais la longueur des dimensions supplémentaires est nécessaire pour permettre au compilateur de calculer correctement les décalages. Dans l'exemple suivant, la
foo
fonction reçoit un pointeur vers un tableau à deux dimensions.La taille de la première dimension
[10]
est ignorée; le compilateur ne vous empêchera pas d'indexer la fin (notez que le formel veut 10 éléments, mais le réel n'en fournit que 2). Cependant, la taille de la deuxième dimension[20]
est utilisée pour déterminer la foulée de chaque ligne, et ici, le formel doit correspondre au réel. Là encore, le compilateur ne vous empêchera pas non plus d'indexer la fin de la deuxième dimension.Le décalage d'octet entre la base du tableau et un élément
args[row][col]
est déterminé par:Notez que si
col >= 20
, alors vous indexerez réellement dans une ligne suivante (ou à la fin du tableau entier).sizeof(args[0])
, revient80
sur ma machine oùsizeof(int) == 4
. Cependant, si j'essaye de prendresizeof(args)
, j'obtiens l'avertissement suivant du compilateur:Ici, le compilateur avertit qu'il ne donnera que la taille du pointeur dans lequel le tableau s'est désintégré au lieu de la taille du tableau lui-même.
la source
args
. Dans ce cas, le premier élément de args est un "tableau de 20 ints". Les pointeurs incluent des informations de type; ce qui est passé est "pointeur vers un tableau de 20 pouces".int (*)[20]
type est; "pointeur vers un tableau de 20 pouces".Le problème et comment le surmonter en C ++
Le problème a été largement expliqué par pat et Matt . Le compilateur ignore fondamentalement la première dimension de la taille du tableau en ignorant effectivement la taille de l'argument passé.
En C ++, en revanche, vous pouvez facilement surmonter cette limitation de deux manières:
std::array
(depuis C ++ 11)Références
Si votre fonction essaie uniquement de lire ou de modifier un tableau existant (sans le copier), vous pouvez facilement utiliser des références.
Par exemple, supposons que vous souhaitiez avoir une fonction qui réinitialise un tableau de dix
int
s en définissant chaque élément sur0
. Vous pouvez facilement le faire en utilisant la signature de fonction suivante:Non seulement cela fonctionnera très bien , mais cela imposera également la dimension du tableau .
Vous pouvez également utiliser des modèles pour rendre le code ci-dessus générique :
Et enfin, vous pouvez profiter de l'
const
exactitude. Considérons une fonction qui imprime un tableau de 10 éléments:En appliquant le
const
qualificatif, nous évitons d'éventuelles modifications .La classe de bibliothèque standard pour les tableaux
Si vous considérez la syntaxe ci-dessus à la fois laide et inutile, comme je le fais, nous pouvons la jeter dans la boîte et l'utiliser à la
std::array
place (depuis C ++ 11).Voici le code refactoré:
N'est-ce pas merveilleux? Sans oublier que l' astuce de code générique que je vous ai enseignée plus tôt fonctionne toujours:
Non seulement cela, mais vous obtenez une copie et un déplacement sémantique gratuitement. :)
Alors qu'est-ce que tu attends? Allez utiliser
std::array
.la source
C'est une fonctionnalité amusante de C qui vous permet de vous tirer efficacement dans le pied si vous êtes si incliné.
Je pense que la raison en est que C est juste un pas au-dessus du langage d'assemblage. La vérification de la taille et les fonctions de sécurité similaires ont été supprimées pour permettre des performances optimales, ce qui n'est pas une mauvaise chose si le programmeur est très diligent.
En outre, attribuer une taille à l'argument de fonction a l'avantage que lorsque la fonction est utilisée par un autre programmeur, il y a une chance qu'il remarque une restriction de taille. Le simple fait d'utiliser un pointeur ne transmet pas cette information au prochain programmeur.
la source
Premièrement, C ne vérifie jamais les limites du tableau. Peu importe qu'ils soient des paramètres locaux, globaux, statiques, peu importe. Vérifier les limites du tableau signifie plus de traitement, et C est censé être très efficace, donc la vérification des limites du tableau est effectuée par le programmeur en cas de besoin.
Deuxièmement, il existe une astuce qui permet de passer par valeur un tableau à une fonction. Il est également possible de renvoyer par valeur un tableau à partir d'une fonction. Il vous suffit de créer un nouveau type de données à l'aide de struct. Par exemple:
Vous devez accéder aux éléments comme ceci: foo.a [1]. Le ".a" supplémentaire peut sembler étrange, mais cette astuce ajoute de grandes fonctionnalités au langage C.
la source
Pour indiquer au compilateur que myArray pointe vers un tableau d'au moins 10 pouces:
Un bon compilateur devrait vous donner un avertissement si vous accédez à myArray [10]. Sans le mot-clé "statique", le 10 ne signifierait rien du tout.
la source
[static]
permet au compilateur d'avertir si vous appelezbar
avec unint[5]
. Il ne dicte pas ce que vous pouvez accéder à l' intérieurbar
. Le fardeau est entièrement du côté de l'appelant.error: expected primary-expression before 'static'
jamais vu cette syntaxe. il est peu probable que ce soit du C ou du C ++ standard.Il s'agit d'une "fonctionnalité" bien connue de C, passée au C ++ car C ++ est censé compiler correctement le code C.
Le problème découle de plusieurs aspects:
On pourrait dire que les tableaux ne sont pas vraiment supportés en C (ce n'est pas vraiment vrai, comme je le disais auparavant, mais c'est une bonne approximation); un tableau est en fait traité comme un pointeur vers un bloc de données et accessible à l'aide de l'arithmétique du pointeur. Puisque C n'a AUCUNE forme de RTTI, vous devez déclarer la taille de l'élément du tableau dans le prototype de fonction (pour prendre en charge l'arithmétique du pointeur). Ceci est encore «plus vrai» pour les tableaux multidimensionnels.
Quoi qu'il en soit, tout ce qui précède n'est plus vraiment vrai: p
La plupart des compilateurs C / C ++ modernes prennent en charge la vérification des limites, mais les normes exigent qu'elle soit désactivée par défaut (pour la compatibilité descendante). Les versions raisonnablement récentes de gcc, par exemple, font une vérification de la plage au moment de la compilation avec "-O3 -Wall -Wextra" et une vérification des limites à l'exécution complète avec "-fbounds-checking".
la source
struct MyStruct s = { .field1 = 1, .field2 = 2 };
) pour initialiser les structures, car c'est un moyen tellement plus clair d'initialiser une structure. En conséquence, la plupart du code C actuel sera rejeté par les compilateurs C ++ standard, car la plupart du code C initialiseront des structures.C ne transformera pas seulement un paramètre de type
int[5]
en*int
; compte tenu de la déclarationtypedef int intArray5[5];
, il transformera un paramètre de typeintArray5
à*int
aussi bien. Il y a des situations où ce comportement, bien qu'étrange, est utile (en particulier avec des éléments comme leva_list
defined instdargs.h
, que certaines implémentations définissent comme un tableau). Il serait illogique d'autoriser comme paramètre un type défini commeint[5]
(ignorant la dimension) mais ne pas permettreint[5]
d'être spécifié directement.Je trouve que la gestion par C des paramètres de type tableau est absurde, mais c'est une conséquence des efforts pour prendre un langage ad-hoc, dont de grandes parties n'étaient pas particulièrement bien définies ou réfléchies, et essayer de trouver des des spécifications cohérentes avec ce que les implémentations existantes ont fait pour les programmes existants. Beaucoup des bizarreries de C ont un sens vu sous cet angle, en particulier si l'on considère que lorsque beaucoup d'entre elles ont été inventées, de grandes parties du langage que nous connaissons aujourd'hui n'existaient pas encore. D'après ce que j'ai compris, dans le prédécesseur de C, appelé BCPL, les compilateurs ne suivaient pas vraiment très bien les types de variables. Une déclaration
int arr[5];
équivaut àint anonymousAllocation[5],*arr = anonymousAllocation;
; une fois l'allocation mise de côté. le compilateur ne savait ni ne se souciait de savoir siarr
était un pointeur ou un tableau. Lorsqu'il est accédé comme étant l'unarr[x]
ou l' autre*arr
, il serait considéré comme un pointeur quelle que soit la façon dont il a été déclaré.la source
Une chose qui n'a pas encore été répondue est la question réelle.
Les réponses déjà données expliquent que les tableaux ne peuvent pas être passés par valeur à une fonction en C ou C ++. Ils expliquent également qu'un paramètre déclaré comme
int[]
est traité comme s'il avait un typeint *
, et qu'une variable de typeint[]
peut être passée à une telle fonction.Mais ils n'expliquent pas pourquoi il n'a jamais été commis d'erreur de fournir explicitement une longueur de tableau.
Pourquoi le dernier de ces derniers n'est-il pas une erreur?
Une raison à cela est que cela pose des problèmes avec les typedefs.
S'il s'agissait d'une erreur de spécifier la longueur du tableau dans les paramètres de fonction, vous ne pourrez pas utiliser le
myarray
nom dans le paramètre de fonction. Et comme certaines implémentations utilisent des types de tableaux pour des types de bibliothèques standard tels queva_list
, et que toutes les implémentations sont nécessaires pour créerjmp_buf
un type de tableau, il serait très problématique s'il n'y avait pas de moyen standard de déclarer les paramètres de fonction en utilisant ces noms: sans cette capacité, il pourrait ne pas être une implémentation portable de fonctions telles quevprintf
.la source
Il est permis aux compilateurs de pouvoir vérifier si la taille du tableau passé est la même que celle attendue. Les compilateurs peuvent signaler un problème si ce n'est pas le cas.
la source