Bonjour,
est-ce possible de passer un tableau en paramètre d'une fonction sans qu'on lui passe l'adresse ? C'est à dire juste un
tableau de valeurs qu'on peut modifier dans la fonction mais qui ne modifie pas celui envoyé en paramètre de la fonction
?
Question en C
Une solution consiste à créer un autre tableau dans la fonction et de copier le contenu du premier dans le deuxième et de travail avec ce dernier au sein de la fonction
Merci mais justement ça que je voulais contourner, j'ai quelques problèmes avec les tests de performances. Je vais tester un autre algo dans ce cas.
Waouh ! Bob, tu te mets au C !
Ce genre de questions est plutôt à poser à France-IOI mais je veux bien t'expliquer.
Le principe des tableaux statiques en C est de réserver un espace mémoire qui permet de contenir le nombre d'éléments indiqués du type du tableau. Une fois la déclaration et la définition du tableau effectuée, dans la suite du programme, écrire son nom revient à indiquer son adresse de début ; le tableau est ainsi utilisé comme un pointeur constant.
char tab[20u];
char *const p = tab;
Dans cet exemple, pour faire une analogie avec le PHP, on a : tab === p
de vérifié (bien que cet opérateur
n'existe pas en C). Éventuellement, en C++, on a : typeid(tab) == typeid(p)
peut-être ?
En passant :
const char *ptr1; char *const ptr2;
ptr1
est un pointeur vers des caractères constants, tandis que ptr2
est un pointeur constant vers
des caractères.
Donc tab
est en fait également un pointeur constant vers des caractères, dont les vingt premiers sont
alloués de manière automatique (et ils seront désalloués également de manière automatique lorsque le tableau deviendra
hors de portée). Tout ce en supposant que ces déclarations soient effectuées dans un bloc (sans quoi le tableau serait
global).
Du coup, pour en venir aux fonctions :
void fct(char tab[])
Ceci est en réalité strictement identique (et j'en suis navré) à :
void fct(char *tab)
Et cela :
void fct(char tab[30u])
Je n'ai jamais trop su… (Je ne vais rien en dire pour éviter d'écrire des erreurs.)
Du coup, et bien… Tu pointes toujours vers les mêmes cases avec tab
, que ce soit dans la fonction appelante
ou dans la fonction appelée. C'est juste l'adresse du début du tableau qui est passée en copie lors de l'appel à
fct
; impossible de faire appel au contenu entier.
Solution 1 (que je déconseille) :
char tmp_tab[20u];
memcpy(tmp_tab,tab,20u);
fct(tmp_tab);
memcpy
se trouve dans string.h
.
Ici, on appelle donc fct
avec un tableau temporaire.
Solution 2 (que je préfère) :
typedef struct Mes_Caracteres
{ char tab[20u]; } Mes_Caracteres;
void fct(Mes_Caracteres caracteres)
...
Mes_Caracteres caracteres;
fct(caracteres);
J'adore cette solution parce qu'en fait elle me rappelle l'automatisme de la programmation orientée objet : tu utilises
une structure, et celle-ci fonctionne alors réellement comme un type à part entière. Les vingt octets qui sont alloués
pour son unique champ seront copiés pour constituer une nouvelle variable locale à la fonction fct
.
En plus, tu réutilises tab
de la même manière à plusieurs endroits lorsque tu l'utilises dans une fonction.
Cela me paraît alors logique d'unifier cela en un nom qui va contenir ton tableau (mais cette justification ne suffit
pas car alors un simple typedef char Mes_Caracteres[20u];
suffirait).
En fait, j'aime peu les tableaux statiques qui se baladent dans le code, à part si c'est pour s'en servir une fois très
vite. Mais dans les structures, je trouve qu'ils ont leur place.
Je pense avoir été suffisamment exhaustif (j'ai probablement rappelé des concepts que tu connaissais déjà)…
EDIT : Finalement, vu ton dernier message, je suppose que tu n'auras pas besoin de tout ça, mais si ton problème réside
dans la performance, je pense que memcpy
peut t'aider, ou même l'utilisation de structures, car je pense que
les copies qui sont alors effectuées sont bien plus efficaces que les copies faites à la main avec une boucle
for
.
Merci pour ce mini-cours :) Les pointeurs en cours je les ai pas vus énormément en cours. Mais tu m'as bien aidé là,
merci encore. Du coup je vais me pencher vers ta solution n°2, si ça me permet d'éviter une copie de tableau à chaque
appel de ma fonction.
Et aussi à part, j'avais testé ça : *tab2=*tab
Enfin ceci restait moins efficace qu'une boucle for pour copier chaque données.
Merci encore et bonne soirée !
Personnellement, je serais plutôt pour éviter de passer par une structure. Si on a créé cette équivalence dans les paramètres au départ, c'est essentiellement pour éviter de recopier des grosses données sur [ce qu'on nomme sur des systèmes non-exotiques] la pile. En masquant ce fonctionnement avec une structure, on se retrouve avec un objet capable de faire exploser la mémoire si on fait grossir un peu la taille du tableau. Plus que quelques récursions et ça fait des étincelles.
D'ailleurs, la notation type f(type t[25])
est effectivement strictement équivalente à
type f(type *t)
; la taille qui est entre crochets n'est donc plus qu'une indication sémantique pour le
programmeur (pas forcément préférable, généralement). Après, en C99/C11 il y a bien moyen de rendre cette constante un
peu plus utile : type f(type t[static 25])
qui spécifie le nombre minimal d'éléments du tableau que
l'appelant est censé envoyer, mais c'est une syntaxe qui se voit rarement (et pour cause, c'est juste pour permettre
éventuellement quelques optis du compilo, à l'instar de restrict
& Cie)...
En clair, personnellement, je privilégierais la solution à base de memcpy
, plus dans l'esprit du langage,
plus secure (tu vois clairement la taille de ce que tu manipules), et même plus élégant (une structure unitaire en
plein milieu du code, wtf ?). L'autre solution, ça me fait plus penser à un hack fait main.
@bob : Je ne suis pas sûr de comprendre quand tu écris *tab2=*tab
. Parce que si tu fais ça, ça devrait être
strictement équivalent à : tab2[0u]=tab[0u]
…
@lucas :
En masquant ce fonctionnement avec une structure, on se retrouve avec un objet capable de faire exploser la mémoire si on fait grossir un peu la taille du tableau.
Je suis d'accord, mais n'est-ce pas la magie de la programmation bas niveau ? À mon avis, c'est au programmeur de faire attention à ce qu'il manipule.
la solution à base de memcpy, plus dans l'esprit du langage
Là, je me plie… C'est vrai que des tableaux dans des structures, malgré tout, ça se voit assez rarement.
plus secure (tu vois clairement la taille de ce que tu manipules)
Oui mais ça devient redondant du coup, trouvé-je. Je reconnais qu'avec sizeof
, ça passe quand même, et
comme il est là, ça reste secure : memcpy(tab2,tab,sizeof(tab));
Mon habitude d'utiliser les structures de telle manière vient donc d'une tentative d'analogie avec la programmation
orientée objet, comme je l'ai déjà dit ; du coup, c'est vrai que ça se justifie assez peu ici…
En fait, dans mes programmes en C, il m'arrive fréquemment de créer des structures de ce genre (souvent avec plusieurs
champs, qui sont fréquemment des structures ou des tableaux de structures aussi), de n'en créer qu'une seule instance,
puis de la passer dans toutes les autres fonctions du programme, par adresse. C'est beaucoup plus facile à manipuler que
plein de variables toutes seules ou même des tableaux à mon sens.
Du coup, le cas des recopies m'arrive rarement ; en général, notamment dans une fonction récursive, je fournis un
pointeur, je modifie la structure dans la fonction puis je lui rends son état initial à la fin de la fonction… Mais si
je le rencontrais à nouveau, je pense bien que j'utiliserais une structure pour effectuer cette recopie.
> Je suis d'accord, mais n'est-ce pas la magie de la programmation bas niveau ? À mon avis, c'est au programmeur de faire attention à ce qu'il manipule.
Je n'aime pas trop cette excuse (c'est loin d'être la première fois que je la lis) parce que le C est déjà un langage non évident à pratiquer ; si on peut dégager un peu la responsabilité du programmeur, c'est tant mieux. Ca ne me semble pas être le genre de choses sur lesquelles il faut cracher.
> C'est vrai que des tableaux dans des structures, malgré tout, ça se voit assez rarement.
Là encore, il me paraît important de privilégier les idiomes du langage, ne serait-ce "que" pour la relecture du code.
> Oui mais ça devient redondant du coup, trouvé-je.
J'aime beaucoup l'abstraction (je n'ai sans doute pas choisi le bon langage !), et je me plais à suivre la programmation
par contrat : si il y a une erreur dans une fonction du module, il faut que ce soit la faute de l'appelant. Dans notre
cas, si je veux oeuvrer sur une copie du tableau dans une de mes fonctions, je ne fais pas le memcpy
dans
l'appelant (sauf si la copie du tableau à un rapport logique avec le but de la fonction appelée), mais dans l'appelé. On
a donc également une taille en paramètre, ensuite utilisée pour allouer dynamiquement, etc.
Ce n'est pas une garantie de sécurité, puisque je n'ai pas le contrôle sur la concordance entre la taille envoyée et celle des données. Par contre, si une erreur arrive, c'est que l'appelant n'a pas respecté le contrat : tant pis pour lui, de toute manière je ne peux pas travailler sur des données qui ne sont pas cohérentes. Utiliser sur ce modèle permet, je trouve, de construire et de tester plus facilement chaque jeu de fonctions. Après, c'est une conception comme une autre, elle a ses avantages comme ses inconvénients, etc.
Sous cet angle, l'utilisation des structures"pour regrouper des données ayant pour seul lien entre elles qu'elles vont être utilisées dans une même série de procédures, me paraît peu justifié. Mais ce n'est que mon avis. ;)
le C est déjà un langage non évident à pratiquer
Ah… Je le trouve plutôt facile d'utilisation, de mon côté, mais c'est probablement parce que j'en fais depuis six ans. Une fois que tu le connais à fond, les erreurs ne surgissent pas si facilement.
si on peut dégager un peu la responsabilité du programmeur, c'est tant mieux
je me plais à suivre la programmation par contrat
Euh, oui… Les deux me paraissent incompatibles, mais avec des assert
dans des #ifdef
, ça devrait
le faire. Pour pousser le bouchon, quand tu effectues une recopie d'une structure, tu n'as qu'à faire un
assert(sizeof(struct_instance)) <= MAX_SIZE
pour éviter de grosses copies ?
l'utilisation des structures pour regrouper des données ayant pour seul lien entre elles qu'elles vont être utilisées dans une même série de procédures, me paraît peu justifié
Ce n'est en réalité pas leur seul lien, mais j'avoue que j'aimerais bien trouver une meilleure méthode, et je doute que
les variables globales puissent m'aider à cela. Si je parviens à trouver un nom qui les réunit, c'est quand même qu'il
existe un semblant de raison qui m'encourage à les regrouper ; mais peut-être que quand je crée des types avec
Info
ou Data
dans leur nom, je dois me poser plus de questions ?
Il existe apparemment un fossé entre la programmation par contrat et la POO. En même temps, je me demande si cette dernière me plaît vraiment, quand je vois que le Java et les langages de Microsoft m'attirent peu…
sam, 28/12/2013 - 00:02 — bob blandin
>
sam, 27/12/2013 - 23:17 — Houllad
>> Une solution consiste à créer un autre tableau dans la fonction et de
>> copier le contenu du premier dans le deuxième et de travail avec ce
>> dernier au sein de la fonction
> Merci mais justement ça que je voulais contourner, j'ai quelques
> problèmes avec les tests de performances. Je vais tester un autre algo
> dans ce cas.
Tes demandes sont incohérentes, si tu veux pouvoir garder la valeur de N
tableaux, il te faut nécessairement N tableaux (ou utiliser une
structure de donnée un peu plus complexe).
Quant à comment procéder pour faire cette copie, je t'invite à lire les
messages de lucas84.
En général, évitez de mettre les gros tableaux (typiquement les tuyaux
dans les problèmes du QCM) sur la pile (aka "with automatic storage
duration" en C++.)
sam, 28/12/2013 - 13:17 — Sylvain CHIRON
> Oui mais ça devient redondant du coup, trouvé-je. Je reconnais qu'avec
> sizeof, ça passe quand même, et comme il est là, ça reste secure :
> memcpy(tab2,tab,sizeof(tab));
Faites bien attention quand vous utilisez sizeof, son fonctionnement est
non trivial. Par exemple en C++11, §5.3.3 ¶2 :
> [...]
> When applied to an array, the result is the total number of bytes in
> the array. This implies that the size of an array of n elements is n
> times the size of an element.
C'est un des cas où la conversion standard "array-to-pointer" N'est PAS
appliquée (sinon on se retrouverait toujours avec sizeof(int*)).
Mais le code de Sylvain fonctionne seulement pour les « vrais »
tableaux, int t[10] n'est pas un tableau lorsqu'il est dans la liste des
paramètres d'une fonction et la plus part du temps, vous travaillerez
sur des tableaux alloués dynamiquement (et donc sur des pointeurs, dont
le sizeof ne vous donne pas la bonne information pour le memcpy.)
Plus généralement, il ne faut pas voir un tableau comme un type
fondamental (c'est un type composite), ils ne se comportent pas de la
même manière que des objets de type int, par exemple C++11, §8.3.4 ¶5 :
> [ Note : [...] Objects of array types cannot be modified, see 3.10. --
> end note ]
Les objets des types tableaux ne peuvent pas être modifiés, ce qui est
assez évident une fois que vous avez compris ce qu'est un tableau en C
ou en C++, d'ailleurs le pointeur renvoyé par une conversion
array-to-pointer est une prvalue en C++.
Tes demandes sont incohérentes, si tu veux pouvoir garder la valeur de N
tableaux, il te faut nécessairement N tableaux (ou utiliser une
structure de donnée un peu plus complexe).
Oui c'est juste que mon algo est pas top, et du coup je cherche une autre solution qui empêche la copie, et copie... du tableau
Une autre solution consiste à appeler la fonction autant de fois qu'il ya d'éléments dans le tableau, tout ce ferra dans une boucle où à chaque itération la fonction est appelé avec comme paramètre un élément i du tableau
Remarque toute bête (et peut-être hs vu que je ne sais pas comment tu utilises tes tableaux):
Certains algorithmes ne nécessitent pas forcément la création de nouveaux tableaux, mais simplement la mémorisation des
éléments changés pour ensuite appliquer la transformation inverse:
Exemple:
1 2 3 4 5 6 7 8 9 10 11 | void blabla(int tabOriginal[42],int indice) { if(indice==42) {verify(tabOriginal);return;} for(truc in Machins) { int tabCopie[42]; memcopy(tabOriginal,tabCopie,42); tabCopie[indice]+=truc; blabla(tabCopie,indice+1); } } |
peut être remplacé par
1 2 3 4 5 6 7 8 9 10 | void blabla(int tabToutCourt[42],int indice) { if(indice==42) {verify(tabToutCourt);return;} for(truc in Machins) { tabToutCourt[indice]+=truc; blabla(tabToutCourt,indice+1); tabToutCourt[indice]-=truc; } } |
Je l'ai effectivement évoqué en disant :
« le cas des recopies m'arrive rarement ; en général, notamment dans une fonction récursive, je fournis un pointeur, je modifie la structure dans la fonction puis je lui rends son état initial à la fin de la fonction… »
Quoique c'était peu clair et vite dit (j'aurais peut-être dû parler de tableau plutôt que de structures).
Il y a aussi le cas ou la modification du tableau n'a pas d'incidence sur la suite de l'algorithme.
@Sylvain: Désolé, j'ai lu en diagonale et j'ai cru que ça n'avait pas encore été évoqué.