135 votes

Pourquoi ne compilateurs C et C++ permettre tableau des longueurs en fonction des signatures quand elles ne sont jamais appliquées?

C'est ce que j'ai trouvé lors de 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]) , [1] semble ne rien faire et ne fonctionne pas
tous, parce que je peux utiliser a[2]. Tout comme int a[] ou char *a. Je sais que le nom de tableau est un pointeur et la manière de transmettre un tableau, donc, mon puzzle n'est pas sur cette partie.

Ce que je veux savoir, c'est pourquoi les compilateurs permettre ce comportement (int a[1]). Ou faut-il avoir d'autres significations, que je ne le sais pas?

158voto

Matt McNabb Points 14273

C'est un caprice de la syntaxe du passage de tableaux à des fonctions.

En fait, il n'est pas possible de passer un tableau en C. Si vous écrivez une syntaxe qui ressemble à passer le tableau, ce qui se passe réellement est qu'un pointeur vers le premier élément du tableau est transmis à la place.

Depuis le pointeur de ne pas comprendre toute la longueur de l'information, le contenu de votre [] dans la fonction officielle de la liste des paramètres sont en fait ignoré.

La décision d'autoriser cette syntaxe a été faite dans les années 1970 et a causé beaucoup de confusion depuis...

144voto

pat Points 6761

La longueur de la première dimension est ignoré, mais la longueur de dimensions supplémentaires sont nécessaires pour permettre au compilateur de calculer les décalages correctement. Dans l'exemple suivant, l' foo fonction en paramètre un pointeur vers un tableau à deux dimensions.

#include <stdio.h>

void foo(int args[10][20])
{
    printf("%zd\n", sizeof(args[0]));
}

int main(int argc, char **argv)
{
    int a[2][20];
    foo(a);
    return 0;
}

La taille de la première dimension, [10] est ignoré; le compilateur ne vous empêchera pas de l'indexation, à la fin (notez que le formel veut de 10 éléments, mais ne dispose que de 2). Cependant, la taille de la seconde dimension [20] est utilisé pour déterminer la foulée de chaque ligne, et ici, l'officiel doit correspondre à la réalité. Encore une fois, le compilateur ne vous empêchera pas de l'indexation, à la fin de la deuxième dimension.

Le décalage d'octets à partir de la base de la matrice à un élément args[row][col] est déterminé par:

sizeof(int)*(col + 20*row)

Notez que si col >= 20, alors vous allez effectivement index dans une rangée suivante (ou à la fin de l'ensemble de la matrice).

sizeof(args[0]), les retours 80 sur ma machine où sizeof(int) == 4. Cependant, si j'ai tenter de prendre en sizeof(args), j'obtiens le message d'avertissement du compilateur:

foo.c:5:27: warning: sizeof on array function parameter will return size of 'int (*)[20]' instead of 'int [10][20]' [-Wsizeof-array-argument]
    printf("%zd\n", sizeof(args));
                          ^
foo.c:3:14: note: declared here
void foo(int args[10][20])
             ^
1 warning generated.

Ici, le compilateur est en garde que il ne va donner la taille du pointeur dans la pile de disques a pourri au lieu de la taille de la matrice elle-même.

33voto

Jefffrey Points 31698

Le problème et comment le surmonter en C++

Le problème a été explique largement par pat et Mat. Le compilateur est fondamentalement ignorant la première dimension de la taille du tableau efficacement en ignorant la taille de l'argument passé.

En C++, d'autre part, vous pouvez facilement surmonter cette limitation de deux façons:

  • à l'aide de références
  • à l'aide de std::array (depuis C++11)

Références

Si votre fonction est seulement d'essayer de lire ou de modifier un groupe existant (pas la copie), vous pouvez facilement utiliser des références.

Par exemple, supposons que vous voulez avoir une fonction qui permet de réinitialiser un ensemble de dix ints réglage de chaque élément d' 0. Vous pouvez facilement le faire en utilisant la fonction suivante signature:

void reset(int (&array)[10]) { ... }

Non seulement cela va fonctionner très bien, mais il permettra également de faire respecter la dimension de la matrice.

Vous pouvez également faire usage de modèles pour rendre le code ci-dessus générique:

template<class Type, std::size_t N>
void reset(Type (&array)[N]) { ... }

Et enfin, vous pouvez profiter de l' const d'exactitude. Considérons une fonction qui imprime un tableau de 10 éléments:

void show(const int (&array)[10]) { ... }

Par l'application de l' const qualificatif de nous prévenir d'éventuelles modifications.


La bibliothèque standard de classe pour les tableaux

Si vous considérez la syntaxe ci-dessus à la fois laid et inutile, comme je le fais, nous pouvons jeter dans le pouvez et de les utiliser std::array à la place (depuis C++11).

Voici la refactorisation de code:

void reset(std::array<int, 10>& array) { ... }
void show(std::array<int, 10> const& array) { ... }

N'est-il pas merveilleux? Pour ne pas mentionner que le code générique truc que j'ai appris plus tôt, fonctionne encore:

template<class Type, std::size_t N>
void reset(std::array<Type, N>& array) { ... }

template<class Type, std::size_t N>
void show(const std::array<Type, N>& array) { ... }

Non seulement cela, mais vous obtenez de copie et de déplacement sémantique pour gratuit. :)

void copy(std::array<Type, N> array) {
    // a copy of the original passed array 
    // is made and can be dealt with indipendently
    // from the original
}

Alors, qu'attendez-vous pour? Go utiliser std::array.

9voto

bill Points 96

C'est un plaisir de fonction de C , qui permet effectivement de se tirer dans le pied si vous êtes si incliné.

Je pense que la raison est que C est juste un cran au-dessus de l'assemblée de la langue. La taille de la vérification et de sécurité similaires ont été supprimés pour permettre des performances de pointe, ce qui n'est pas une mauvaise chose si le programmeur est très diligent.

Aussi, l'affectation d'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'ils vous remarquerez une restriction de taille. Simplement en utilisant un pointeur de ne pas transmettre cette information à la prochaine programmeur.

8voto

user34814 Points 85

Tout d'abord, C ne vérifie jamais les limites du tableau. N'a pas d'importance si elles sont locales, globales et statiques, paramètres, que ce soit. Vérification des limites du tableau signifie plus de traitement, et C est censé être très efficace, de sorte que les limites du tableau de contrôle est fait par le programmeur en cas de besoin.

Deuxièmement, il existe une astuce qui permet de faire passer-par-valeur d'un tableau à une fonction. Il est également possible de revenir en valeur un tableau à partir d'une fonction. Vous avez juste besoin de créer un nouveau type de données à l'aide struct. Par exemple:

typedef struct {
  int a[10];
} myarray_t;

myarray_t my_function(myarray_t foo) {

  myarray_t bar;

  ...

  return bar;

}

Vous avez accès à des éléments comme ceci: foo.[1]. L'extra ".un" peut sembler étrange, mais cette astuce ajoute beaucoup de fonctionnalités au langage C.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X