web-dev-qa-db-fra.com

Longueur du tableau dans l'argument de la fonction

C'est un code bien connu pour calculer la longueur d'un tableau en C:

sizeof(array)/sizeof(type)

Mais je n'arrive pas à trouver la longueur du tableau passé en argument à une fonction:

#include <stdio.h>

int length(const char* array[]) {
  return sizeof(array)/sizeof(char*);
}

int main() {
  const char* friends[] = { "John", "Jack", "Jim" };
  printf("%d %d", sizeof(friends)/sizeof(char*), length(friends)); // 3 1
}

Je suppose que le tableau est copié par valeur dans l'argument de la fonction en tant que pointeur constant et que la référence à ce dernier devrait résoudre ce problème, mais cette déclaration n'est pas valide:

int length(const char**& array);

Je trouve que transmettre la longueur du tableau comme second argument est une information redondante, mais pourquoi la déclaration standard de main ressemble-t-elle à ceci:

int main(int argc, char** argv);

Veuillez expliquer s'il est possible de connaître la longueur du tableau dans l'argument de la fonction et, dans l'affirmative, pourquoi la redondance existe-t-elle dans main.


58
Jan Turoň

sizeof ne fonctionne que pour trouver la longueur du tableau si vous l'appliquez au tableau d'origine.

int a[5]; //real array. NOT a pointer
sizeof(a); // :)

Cependant, au moment où le tableau se décompose en un pointeur, sizeof donnera la taille du pointeur et non celle du tableau.

int a[5];
int * p = a;
sizeof(p); // :(

Comme vous l'avez déjà clairement fait remarquer, main reçoit la longueur du tableau sous forme d'argument (argc). Oui, c'est une nécessité et n'est pas redondant. (Et bien, c'est un peu redondant puisque argv est facilement terminé par un pointeur nul mais je m'éloigne du sujet)

Il y a un raisonnement qui explique pourquoi cela se produirait. Comment pourrions-nous faire des choses pour qu'un tableau C connaisse aussi sa longueur?

Une première idée serait de ne pas décomposer les tableaux en indicateurs lorsque ceux-ci sont passés à une fonction et de conserver la longueur du tableau dans le système de types. Le problème est que vous auriez besoin d’une fonction distincte pour chaque longueur de tableau possible. Ce n’est pas une bonne idée. (Pascal a fait cela et certaines personnes pensent que c'est l'une des raisons pour lesquelles il a été "perdu" par C)

Une autre idée est de stocker la longueur du tableau à côté du tableau, comme tout langage de programmation moderne:

a -> [5];[0,0,0,0,0]

Mais alors vous créez simplement un struct invisible dans les coulisses et la philosophie C n’approuve pas ce genre de surcharge. Cela dit, créer vous-même une telle structure est souvent une bonne idée pour certains problèmes:

struct {
    size_t length;
    int * elements;
}

Une autre chose à laquelle vous pouvez penser est la manière dont les chaînes en C sont terminées par un zéro au lieu de stocker une longueur (comme en Pascal). Pour stocker une longueur sans se soucier des limites, vous avez besoin de énorme quatre octets, un montant incroyablement coûteux (du moins à l'époque). On pourrait se demander si les tableaux peuvent également être terminés par une valeur NULL, mais comment autoriseriez-vous le tableau à stocker une valeur NULL?

60
hugomg

Le tableau se désintègre en un pointeur lorsqu'il est transmis.

Section 6.4 de la FAQ C couvre très bien cela et fournit les références K & R, etc.


Cela dit, imaginons que la fonction puisse connaître la taille de la mémoire allouée dans un pointeur. Vous pouvez appeler la fonction deux fois ou plus, chaque fois avec différents tableaux d’entrée de longueurs potentiellement différentes; la longueur devrait donc être passée comme une variable cachée secrète. Et ensuite, considérez si vous avez passé un offset dans un autre tableau, ou un tableau alloué sur le tas (malloc et toutes les fonctions de bibliothèque - quelque chose auquel le compilateur se connecte, plutôt que de voir et de raisonner sur le corps de).

Il devient difficile d’imaginer comment cela pourrait fonctionner sans certains objets en tranches dans les coulisses et ce n’est-ce pas?


Symbian avait une fonction AllocSize() qui renvoyait la taille d'une allocation avec malloc(); cela n'a fonctionné que pour le pointeur littéral renvoyé par le malloc, et vous obtiendriez un gobbledygook ou un crash si vous lui demandiez de connaître la taille d'un pointeur non valide ou d'un décalage de pointeur par rapport à un.

Vous ne voulez pas croire que ce n'est pas possible, mais ce n'est vraiment pas le cas. Le seul moyen de connaître la longueur d'un élément transmis à une fonction est de le suivre vous-même et de le transmettre en tant que paramètre explicite distinct.

12
Will

Comme indiqué par @Will, la décroissance se produit lors du passage du paramètre. Une façon de contourner ce problème consiste à transmettre le nombre d'éléments. Pour ajouter à cela, vous pouvez trouver la macro _countof() utile - elle équivaut à ce que vous avez fait;)

2
Mike Kwan

En ce qui concerne int principal ():

Selon la norme, argv pointe vers un tableau NULL -terminé (des pointeurs vers des chaînes terminées par un caractère nul). (5.1.2.2.1: 1).

C'est-à-dire argv = (char **){ argv[0], ..., argv[argc - 1], 0 };.

Par conséquent, le calcul de la taille est effectué par une fonction qui est une modification triviale de strlen().

argc n'est là que pour effectuer le calcul de la longueur argv O (1).

La méthode count-before-NULL fonctionnera ET NON pour l'entrée de tableau générique. Vous devrez spécifier manuellement la taille en tant que second argument.

2
moshbear

Premièrement, une meilleure utilisation pour calculer le nombre d'éléments lorsque la déclaration de tableau réelle est dans la portée est la suivante:

sizeof array / sizeof array[0]

De cette façon, vous ne répétez pas le nom du type, ce qui pourrait bien sûr changer dans la déclaration et vous amener à un calcul de longueur incorrect. C'est un cas typique de ne vous répétez pas .

Deuxièmement, notez que sizeof n’est pas une fonction. Par conséquent, l’expression ci-dessus ne nécessite aucune parenthèse autour de l’argument de sizeof.

Troisièmement, C n’ayant pas de références, votre utilisation de & Dans une déclaration ne fonctionnera pas.

Je conviens que la solution C appropriée consiste à passer la longueur (en utilisant le type size_t) Comme argument séparé et à utiliser sizeof à l'endroit où l'appel est effectué si l'argument est un " "réel" tableau.

Notez que vous travaillez souvent avec de la mémoire renvoyée, par exemple. malloc(), et dans ces cas, vous ne disposez jamais d'un "vrai" tableau pour calculer la taille, donc la conception de la fonction pour utiliser un nombre d'éléments est plus flexible.

2
unwind

C'est une vieille question et le PO semble mélanger C++ et C dans ses exemples/exemples. En C, lorsque vous passez un tableau à une fonction, il se décompose en pointeur. Donc, il n'y a aucun moyen de passer la taille du tableau sauf en utilisant un deuxième argument dans votre fonction qui stocke la taille du tableau:

void func(int A[]) 
// should be instead: void func(int * A, const size_t elemCountInA)

Il y a très peu de cas où vous n'en avez pas besoin, comme lorsque vous utilisez des tableaux multidimensionnels:

void func(int A[3][whatever here]) // That's almost as if read "int* A[3]"

L'utilisation de la notation de tableau dans une signature de fonction est toujours utile, pour le développeur, car cela peut être une aide pour dire le nombre d'éléments attendus par vos fonctions. Par exemple:

void vec_add(float out[3], float in0[3], float in1[3])

est plus facile à comprendre que celui-ci (même si rien n'empêche d'accéder au 4ème élément de la fonction dans les deux fonctions):

void vec_add(float * out, float * in0, float * in1)

Si vous utilisiez C++, vous pouvez alors capturer la taille du tableau et obtenir ce que vous attendez:

template <size_t N>
void vec_add(float (&out)[N], float (&in0)[N], float (&in1)[N])
{
    for (size_t i = 0; i < N; i++) 
        out[i] = in0[i] + in1[i];
}

Dans ce cas, le compilateur s'assurera que vous n'ajoutez pas de vecteur 4D à un vecteur 2D (ce qui n'est pas possible en C sans passer la dimension de chaque dimension en tant qu'argument de la fonction). Il y aura autant d'instances de la fonction vec_add que de nombre de dimensions utilisées pour vos vecteurs.

1
xryl669