77 votes

Que signifie réellement l'impossibilité de renvoyer des tableaux en C?

Je n'essaie pas de reproduire la question habituelle sur les C de ne pas pouvoir retourner les tableaux, mais creuser un peu plus profondément en elle.

Nous ne pouvons pas faire ceci:

char f(void)[8] {
    char ret;
    // ...fill...
    return ret;
}

int main(int argc, char ** argv) {
    char obj_a[10];
    obj_a = f();
}

Mais nous pouvons faire:

struct s { char arr[10]; };

struct s f(void) {
    struct s ret;
    // ...fill...
    return ret;
}

int main(int argc, char ** argv) {
    struct s obj_a;
    obj_a = f();
}

Donc, j'ai eu à parcourir le code ASM généré par gcc -S et semble être de travailler avec la pile, s'adressant -x(%rbp) comme avec tout les autres C de retour de la fonction.

Qu'est-ce avec le retour des tableaux directement? Je veux dire, pas dans les termes de l'optimisation ou de la complexité de calcul, mais en termes de la réelle capacité de le faire sans la structure de la couche.

Données supplémentaires: je suis sous Linux avec gcc sur un x64 Intel.

110voto

Steve Summit Points 16971

Tout d'abord, oui, vous pouvez encapsuler un tableau dans une structure, et puis faire ce que vous voulez avec cette structure (les céder, de le retourner à partir d'une fonction, etc.).

Deuxième de tous, comme vous l'avez découvert, le compilateur a peu de difficulté à en émettant code de retour (ou céder) des structures. Ce n'est donc pas la raison pour laquelle vous ne pouvez pas retourner les tableaux, soit.

La raison fondamentale pour laquelle vous ne pouvez pas faire cela, c'est que, ne mâchons pas nos mots, tableaux de seconde classe des structures de données en C. Toutes les autres structures de données sont de première classe. Quelles sont les définitions de "première classe" et de "deuxième classe" dans ce sens? Tout simplement que les deuxième types de classe ne peut pas être affecté.

(Votre prochaine question est susceptible d'être, "Autres que les tableaux, d'autres de seconde classe, les types de données?", et je pense que la réponse est "Pas vraiment, sauf si vous comptez fonctions".)

Intimement liée avec le fait que vous ne pouvez pas retourner (ou céder) des tableaux est qu'il n'existe pas de valeurs de type tableau, soit. Il y a des objets (variables) de type tableau, mais à chaque fois que vous essayez de prendre la valeur de un, vous obtenez un pointeur vers le premier élément du tableau. [Note de bas de page: plus formellement, il n'y a pas rvalues de type tableau, bien que d'un objet de type tableau peut être considéré comme une lvalue, quoique non cessible un.]

Donc, tout à fait en dehors du fait que vous ne pouvez pas attribuer à un tableau, vous aussi ne peut pas générer une valeur à assigner à un tableau. Si vous dites

char a[10], b[10];
a = b;

c'est comme si vous aviez écrit

a = &b[0];

Nous avons donc un pointeur sur la droite, et un tableau sur la gauche, et nous aurions un massif d'incompatibilité de type, même si les tableaux ont été en quelque sorte un droit cessible. De la même façon (à partir de votre exemple) si l'on essaie d'écrire

a = f();

et quelque part à l'intérieur de la définition de la fonction f() nous avons

char ret[10];
/* ... fill ... */
return ret;

c'est comme si cette dernière ligne dit

return &ret[0];

et, encore une fois, nous n'avons pas de tableau de la valeur de retour et d'attribuer à la a, simplement un pointeur.

(Dans l'appel de fonction exemple, nous avons aussi la question très importante qu' ret est un tableau, périlleux d'essayer de revenir dans C. Plus sur ce point plus tard).

Maintenant, la partie de votre question est probablement "Pourquoi est-il de cette façon?", et aussi: "Si vous ne pouvez pas attribuer les tableaux, pourquoi peut vous attribuer des structures contenant des tableaux?"

Ce qui suit est mon interprétation et mon avis, mais c'est cohérent avec ce que Dennis Ritchie décrit dans l'étude du Développement du Langage C.

La non-cessibilité des tableaux découle de trois faits:

  1. C est destiné à être syntaxiquement et sémantiquement proche du matériel de la machine. Une opération élémentaire en C compiler vers le bas pour un ou plusieurs instructions machine en prenant un ou une poignée de cycles de processeur.

  2. Les tableaux ont toujours été spécial, en particulier dans la façon dont ils se rapportent à des pointeurs; cette relation particulière évolué à partir et a été fortement influencé par le traitement de tableaux en C, le prédécesseur de la langue B.

  3. Les Structures n'étaient initialement pas en C.

En raison pour le point 2, il est impossible d'attribuer les tableaux, et en raison pour le point 1, il ne devrait pas être possible de toute façon, parce qu'un seul opérateur d'assignation = de ne pas l'étendre à de code qui peut prendre N milliers de cycles de copier un N mille élément de tableau.

Et puis nous arrivons au point 3, qui finit par former une contradiction.

Lors de la C a obtenu des structures, ils ont d'abord n'étaient pas entièrement de première classe, soit, en ce que vous ne pouvez pas céder ou de les retourner. Mais la raison pour laquelle vous ne pouvez pas simplement que le premier compilateur n'a pas assez intelligent, au premier abord, pour générer le code. Il n'y avait pas de syntaxe ou de sémantique barrage, comme il y avait pour les tableaux.

Et le but était pour les structures de première classe, et ce résultat a été atteint relativement tôt, peu de temps autour de l'heure à laquelle la première édition de K&R a été l'impression.

Mais la grande question demeure de savoir si une opération élémentaire est censé compiler vers le bas à un petit nombre d'instructions et de cycles, pourquoi ne pas l'argument interdire la structure d'affectation? Et la réponse est, oui, c'est une contradiction.

Je crois (mais c'est plus de la spéculation de ma part) que la pensée était à quelque chose comme ceci: "de Première classe sont les types de bon, la deuxième classe de types sont malheureux. Nous sommes coincés avec de seconde classe pour les tableaux, mais on peut faire mieux avec des structures. Le pas-cher-code de la règle n'est pas vraiment une règle, c'est plus d'une ligne directrice. Les tableaux seront souvent être grand, mais les structures seront généralement petites, des dizaines ou des centaines d'octets, de sorte que leur affectation ne sera pas en général trop cher."

Ainsi une application uniforme de la pas-cher-code règle est tombé à l'eau. C n'a jamais été parfaitement constante, de toute façon. (Ni, d'ailleurs, sont la grande majorité des langues réussie, humaine et artificielle.)

Avec tout cela dit, il peut être la peine de se demander, "Que faire si C n'a le soutien d'affectation et le retour des tableaux? Comment ça fonctionne?" Et la réponse devra impliquer une certaine façon de désactiver le comportement par défaut des tableaux dans les expressions, à savoir qu'ils ont tendance à se transformer en des pointeurs vers son premier élément.

Parfois, dans les années 90, autant que je me souvienne, il y avait un assez bien pensé proposition de faire exactement cela. Je pense que c'impliqués en joignant un tableau de l'expression en [ ] ou [[ ]] ou quelque chose. Aujourd'hui, je n'arrive pas à trouver aucune mention de cette proposition (même si je serais reconnaissant si quelqu'un peut fournir une référence). Pour le moment je vais à émettre l'hypothèse d'un nouvel opérateur ou pseudofunction appelés arrayval().

Nous pourrions étendre C pour permettre tableau d'affectation de la façon suivante:

  1. Suppression de l'interdiction de l'utilisation d'un tableau sur le côté gauche d'un opérateur d'affectation.

  2. Suppression de l'interdiction de la déclaration de la matrice de fonctions à valeur. Revenir à la question initiale, faire char f(void)[8] { ... } juridique.

  3. (C'est le biggie.) Avoir un moyen de faire mention d'une matrice dans une expression et de se retrouver avec un véritable, cessible valeur (une rvalue) de type tableau. Comme mentionné, pour l'instant, je vais poser la syntaxe arrayval( ... ).

[Note de côté: nous avons aujourd'Hui une "clé de la définition" qui

Une référence à un objet de type tableau qui apparaît dans une expression de désintégrations (à trois exceptions près) en un pointeur sur son premier élément.

Les trois exceptions sont quand le tableau est l'opérande d'une sizeof ou & de l'opérateur ou est un littéral de chaîne d'initialiseur pour un tableau de caractères. En vertu de l'hypothétique modifications, je suis en train de parler ici, il y aurait quatre exceptions, avec l'opérande de l' arrayval opérateur d'être ajouté à la liste.]

De toute façon, avec ces modifications en place, nous pourrions écrire des choses comme

char a[8], b[8] = "Hello";
a = arrayval(b);

(Évidemment, il faudrait aussi décider quoi faire si l' a et b n'étaient pas de la même taille.)

Compte tenu de la fonction de prototype

char f(void)[8];

nous pourrions aussi le faire

a = f();

Regardons fs'hypothétique définition. Nous pourrions avoir quelque chose comme

char f(void)[8] {
    char ret[8];
    /* ... fill ... */
    return arrayval(ret);
}

Il faut noter que, à l'exception de l'hypothétique nouveau arrayval() opérateur) c'est à peu près ce que Dario Rodriguez posté. Notez également que, dans le monde hypothétique où les tableaux d'affectation était légal, et quelque chose comme arrayval() existaient -- ce serait en fait le travail! En particulier, il ne serait pas subir le problème de la restitution de bientôt-à-être-pointeur non valide pour le local array ret. Il serait de retour une copie du tableau, donc il n'y aurait pas de problème, ce serait à peu près parfaitement analogue à l'évidence-juridique

int g(void) {
    int ret;
    /* ... compute ... */
    return ret;
}

Enfin, de retour sur le côté à la question "existe-il d'autres second-types de classe?", Je pense que c'est plus qu'une coïncidence que les fonctions, comme des tableaux, automatiquement leur adresse quand ils ne sont pas utilisés comme eux-mêmes (qui est, comme des fonctions ou des tableaux), et qu'il y a même pas de rvalues de type fonction. Mais c'est surtout une vaine rêverie, parce que je ne pense pas que j'ai jamais entendu parler de fonctions appelé "deuxième classe" types de C. (peut-être qu'ils ont, et je l'ai oublié.)

23voto

John Bollinger Points 16563

Qu'est-ce avec le retour des tableaux directement? Je veux dire, pas dans les termes de l'optimisation ou de la complexité de calcul, mais en termes de la réelle capacité de le faire sans la structure de la couche.

Il n'a rien à voir avec la capacité en soi. D'autres langues offrent la possibilité de retourner les tableaux, et vous savez déjà qu'en C, vous pouvez revenir à une structure avec un membre du groupe. D'autre part, pourtant, d'autres langues ont la même limitation que C fait, et plus encore. Java, par exemple, ne peut pas retourner les tableaux, ni d'ailleurs des objets de tout type, à partir des méthodes. Il peut renvoyer uniquement les primitives et les références à des objets.

Non, c'est simplement une question de langage de conception. Comme avec la plupart des autres choses à faire avec les tableaux, la conception des points ici tournent autour de la C de la disposition, les expressions du type tableau sont automatiquement converties à des pointeurs dans presque tous les contextes. La valeur fournie en return déclaration n'est pas une exception, donc C n'a aucun moyen de partager le retour d'un tableau lui-même. Un autre choix aurait pu être fait, mais il n'était tout simplement pas.

5voto

Roman Odaisky Points 798

Pour les tableaux à être les objets de première classe, vous devez vous attendre à moins d'être capable de leur attribuer. Mais cela nécessite la connaissance de la taille, et le type C du système n'est pas assez puissant pour fixer les tailles pour tous les types. C++ pourrait le faire, mais ne fonctionne pas à cause de l'héritage des préoccupations-il a des références à des tableaux de taille particulière (typedef char (&some_chars)[32]), mais la plaine des tableaux sont toujours implicitement converti à pointeurs en C. le C++ a std::array au lieu de cela, ce qui est fondamentalement ladite matrice à l'intérieur de la structure ainsi que certaines sucre syntaxique.

-2voto

Rob Points 45

J'ai peur dans mon esprit, ce n'est pas tellement un débat de première ou de seconde classe des objets, c'est un débat religieux de bonne pratique et de pratique applicables pour la profondeur des applications embarquées.

De retour d'une structure signifie soit une structure de la racine être changé par la ruse dans les profondeurs de la séquence d'appel, ou une duplication des données et le transfert de gros morceaux de données dupliquées. Les principales applications de C sont encore largement concentrée autour de la profonde applications embarquées. Dans ces domaines, vous avez les petits producteurs qui n'ont pas besoin d'être grande transmission de blocs de données. Vous avez également la pratique technique qui nécessite le besoin d'être en mesure de fonctionner sans dynamique de l'allocation de RAM, et avec un minimum de pile et souvent pas de tas. Il pourrait être soutenu que le retour de la structure est la même que la modification via le pointeur, mais prélevée dans la syntaxe... j'ai peur, je dirais que c'est pas dans le C de la philosophie de "ce que vous voyez est ce que vous obtenez" de la même manière un pointeur vers un type est.

Personnellement, je dirais que vous avez trouvé un trou de boucle, si la norme approuvée ou non. C est conçu de telle manière que l'allocation est explicite. Que vous passez une bonne pratique de l'adresse de bus de la taille des objets, normalement dans un ambitieux d'un cycle, se référant à la mémoire qui a été allouée explicitement à un temps contrôlé à l'intérieur de l'développeurs de ken. Cela fait sens en termes de code efficience, l'efficacité du cycle, et qui offre le plus de contrôle et de la clarté de l'objectif. Je crains que, dans le code de l'inspection, je jetterais une fonction retournant une structure comme une mauvaise pratique. C ne pas respecter de nombreuses règles, c'est une langue à des fins professionnelles des ingénieurs à de nombreux égards, il s'appuie sur l'utilisateur de l'application de leur propre discipline. Juste parce que vous pouvez, ne signifie pas que vous devriez... Il offre assez de preuve de balle des moyens pour gérer des données de très complexe de la taille et du type utilisant moment de la compilation, de la rigueur et de minimiser la dynamique des variations de l'empreinte et à l'exécution.

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