Une petite question sur les fonctions en C : depuis quand peut-on en C
imbriquer la définition d'une fonction à l'intérieur d'une autre ?
Je fais du C depuis plusieurs années et je suis tombé récemment sur un
truc du genre :
#include <stdio.h>
void main()
{
int i;
int f (int x)
{
return x * 3;
}
for (i = 0; i < 100; i++) printf ("%d = %d\n", i, f(i));
}
Ca peut-être assez pratique, pour ne pas polluer le code avec des
fonctions qui ne servent que dans une fonction donnée.
Je faisais des trucs du style en Pascal il y a bien longtemps, et je
savais que c'était autorisé en C++, mais je n'aurais jamais eu la
curiosité de le tester en C.
Pour info, ce code a été testé sous Linux avec gcc 4.1.2
Le Sat, 31 Jul 2010 23:05:56 +0000 (UTC), Marc Espie écrivait :
In article <4c54a520$0$16565$, Alain BARTHE wrote:
Bonsoir,
Une petite question sur les fonctions en C : depuis quand peut-on en C imbriquer la définition d'une fonction à l'intérieur d'une autre ?
C'est une extension specifique a gcc.
Ca pose d'eventuels problemes. C'est implemente avec une technique appelee "trampolines", qui necessite dans pas mal de cas de rendre la pile executable, ce qui la rend nettement plus sensible aux attaques de type buffer-overflow...
Que ce soit une extension, peut-être, je ne me suis jamais penché sur ce problème. En revanche, je ne connais pas de compilo qui refuse les fonctions imbriquées. gcc 2.7 l'autorisait déjà, DEC C aussi, le compilo Sun n'est pas gêné... Aurais-tu des exemples ? Je pose la question parce que pour une histoire de lisibilité, j'utilise ça dans quelques programmes et je n'ai jamais eu un utilisateur qui soit venu râler parce que la bibliothèque en question refusait de compiler...
Cordialement,
JKB
Pour info, j'ai un vieux compilateur Sun sur Solaris 9 qui refuse cette
syntaxe :
$ : cc -V
cc: Forte Developer 7 C 5.4 2002/03/09
usage: cc [ options] files. Use 'cc -flags' for details
Le Sat, 31 Jul 2010 23:05:56 +0000 (UTC),
Marc Espie <espie@lain.home> écrivait :
In article <4c54a520$0$16565$426a74cc@news.free.fr>,
Alain BARTHE <alain.barthe.65@free.fr> wrote:
Bonsoir,
Une petite question sur les fonctions en C : depuis quand peut-on en C
imbriquer la définition d'une fonction à l'intérieur d'une autre ?
C'est une extension specifique a gcc.
Ca pose d'eventuels problemes. C'est implemente avec une technique appelee
"trampolines", qui necessite dans pas mal de cas de rendre la pile executable,
ce qui la rend nettement plus sensible aux attaques de type buffer-overflow...
Que ce soit une extension, peut-être, je ne me suis jamais penché
sur ce problème. En revanche, je ne connais pas de compilo qui
refuse les fonctions imbriquées. gcc 2.7 l'autorisait déjà, DEC C
aussi, le compilo Sun n'est pas gêné... Aurais-tu des exemples ? Je
pose la question parce que pour une histoire de lisibilité,
j'utilise ça dans quelques programmes et je n'ai jamais eu un
utilisateur qui soit venu râler parce que la bibliothèque en
question refusait de compiler...
Le Sat, 31 Jul 2010 23:05:56 +0000 (UTC), Marc Espie écrivait :
In article <4c54a520$0$16565$, Alain BARTHE wrote:
Bonsoir,
Une petite question sur les fonctions en C : depuis quand peut-on en C imbriquer la définition d'une fonction à l'intérieur d'une autre ?
C'est une extension specifique a gcc.
Ca pose d'eventuels problemes. C'est implemente avec une technique appelee "trampolines", qui necessite dans pas mal de cas de rendre la pile executable, ce qui la rend nettement plus sensible aux attaques de type buffer-overflow...
Que ce soit une extension, peut-être, je ne me suis jamais penché sur ce problème. En revanche, je ne connais pas de compilo qui refuse les fonctions imbriquées. gcc 2.7 l'autorisait déjà, DEC C aussi, le compilo Sun n'est pas gêné... Aurais-tu des exemples ? Je pose la question parce que pour une histoire de lisibilité, j'utilise ça dans quelques programmes et je n'ai jamais eu un utilisateur qui soit venu râler parce que la bibliothèque en question refusait de compiler...
Cordialement,
JKB
Xavier Roche
Marc Espie a écrit :
Tiens, je savais pas. http://pax.grsecurity.net/docs/emutramp.txt explique en detail ce que ca fait... terrain connu, et solutions connues.
Ah, j'ai pigé la raison du trampoline.
Exemple:
#include <stdio.h> static int callme(int (*f)(int), int i) { return f(i); }
void main() { int i;
int f (int x) { return i * x * 3; }
for (i = 0; i < 100; i++) printf ("%d = %dn", i, callme(f, i)); }
Ce hack permet en fait de trimballer artificiellement un contexte dans un pointeur de fonction, ce qui peut être assez pratique.
[Sauf quand le noyau est prévu pour refuser ce genre d'exotisme évidemment. Sur un tel environnement, le programme ci-dessus va crasher avec quelque chose comme:
Tiens, je savais pas.
http://pax.grsecurity.net/docs/emutramp.txt
explique en detail ce que ca fait... terrain connu, et solutions connues.
Ah, j'ai pigé la raison du trampoline.
Exemple:
#include <stdio.h>
static int callme(int (*f)(int), int i) {
return f(i);
}
void main()
{
int i;
int f (int x) {
return i * x * 3;
}
for (i = 0; i < 100; i++) printf ("%d = %dn", i, callme(f, i));
}
Ce hack permet en fait de trimballer artificiellement un contexte dans
un pointeur de fonction, ce qui peut être assez pratique.
[Sauf quand le noyau est prévu pour refuser ce genre d'exotisme
évidemment. Sur un tel environnement, le programme ci-dessus va crasher
avec quelque chose comme:
Tiens, je savais pas. http://pax.grsecurity.net/docs/emutramp.txt explique en detail ce que ca fait... terrain connu, et solutions connues.
Ah, j'ai pigé la raison du trampoline.
Exemple:
#include <stdio.h> static int callme(int (*f)(int), int i) { return f(i); }
void main() { int i;
int f (int x) { return i * x * 3; }
for (i = 0; i < 100; i++) printf ("%d = %dn", i, callme(f, i)); }
Ce hack permet en fait de trimballer artificiellement un contexte dans un pointeur de fonction, ce qui peut être assez pratique.
[Sauf quand le noyau est prévu pour refuser ce genre d'exotisme évidemment. Sur un tel environnement, le programme ci-dessus va crasher avec quelque chose comme:
Ca sent trop l'implementation de type "tiens je suis en train de faire ma these sur les implementations de clotures, si j'en mettais un bout dans gcc".
Outre le fait que les systemes modernes n'ont plus la pile en executable, c'est quand meme de la generation de code au vol, ce qui sur tous les processeurs decents (c'est-a-dire pas intel et ses cradouilleries) necessite d'invalider des lignes de caches... donc pas du tout terrible en performance (sur intel, il y a de la logique dans le processeur pour invalider les lignes en question automatiquement dans ce contexte, ca n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acceder a de la memoire pas alignee fonctionne, mais rame un peu).
"la solution" serait sans doute similaire a celle utilisee pour les gestionnaires de signaux: a savoir parametrer un peu plus le bout de code genere au vol, de facon a ne plus avoir a le generer au vol, et quitte a se taper un petit bout d'indirection supplementaire, a finalement appeler du code standard, qui ne foutra pas le bordel dans le cache...
In article <4c5599dc$0$10129$426a74cc@news.free.fr>,
Samuel DEVULDER <samuel-dot-devulder@laposte-dot-com> wrote:
Ca sent trop l'implementation de type "tiens je suis en train de faire
ma these sur les implementations de clotures, si j'en mettais un bout
dans gcc".
Outre le fait que les systemes modernes n'ont plus la pile en executable,
c'est quand meme de la generation de code au vol, ce qui sur tous les
processeurs decents (c'est-a-dire pas intel et ses cradouilleries)
necessite d'invalider des lignes de caches... donc pas du tout terrible
en performance (sur intel, il y a de la logique dans le processeur pour
invalider les lignes en question automatiquement dans ce contexte, ca
n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acceder
a de la memoire pas alignee fonctionne, mais rame un peu).
"la solution" serait sans doute similaire a celle utilisee pour les
gestionnaires de signaux: a savoir parametrer un peu plus le bout de code
genere au vol, de facon a ne plus avoir a le generer au vol, et quitte
a se taper un petit bout d'indirection supplementaire, a finalement
appeler du code standard, qui ne foutra pas le bordel dans le cache...
Ca sent trop l'implementation de type "tiens je suis en train de faire ma these sur les implementations de clotures, si j'en mettais un bout dans gcc".
Outre le fait que les systemes modernes n'ont plus la pile en executable, c'est quand meme de la generation de code au vol, ce qui sur tous les processeurs decents (c'est-a-dire pas intel et ses cradouilleries) necessite d'invalider des lignes de caches... donc pas du tout terrible en performance (sur intel, il y a de la logique dans le processeur pour invalider les lignes en question automatiquement dans ce contexte, ca n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acceder a de la memoire pas alignee fonctionne, mais rame un peu).
"la solution" serait sans doute similaire a celle utilisee pour les gestionnaires de signaux: a savoir parametrer un peu plus le bout de code genere au vol, de facon a ne plus avoir a le generer au vol, et quitte a se taper un petit bout d'indirection supplementaire, a finalement appeler du code standard, qui ne foutra pas le bordel dans le cache...
Samuel DEVULDER
Marc Espie a écrit :
In article <4c5599dc$0$10129$, Samuel DEVULDER wrote:
Possible, je n'ai fait que zieuter au code ASM que GCC générait sur x86.
Ca sent trop l'implementation de type "tiens je suis en train de faire ma these sur les implementations de clotures, si j'en mettais un bout dans gcc".
C'est comme ca que ca s'est vraiment passé dans gcc tu penses? J'aimerais bien connaitre qui a eu cette idée d'implémenter les fonctions embarquées, leur limitations et la motivation derrière (callbacks dans des GUI?)
Outre le fait que les systemes modernes n'ont plus la pile en executable, c'est quand meme de la generation de code au vol, ce qui sur tous les
Oui.. Et c'est ca que je trouve suspect dans ce que j'ai vu du code asm.. En même temps la fonction embarquée quand i=3 (la vars locale référencée), n'est pas identique à la fonction quand i=4. Les deux fonctions peuvent être utilisées simultanément et ne pas se mélanger les pieds. Ca me semble nécéssiter une notion de génération dynamique de "quelque chose d'executable" qui capture l'état de la pile du prog à ce moment là.
En fait chaque référence utilisée de la fonction a potentiellement un contexte différent. On a donc affaire à plusieurs fonctions distinctes les unes des autres du point de vu "bas niveau" à chaque référence d'une telle fonction.
processeurs decents (c'est-a-dire pas intel et ses cradouilleries) necessite d'invalider des lignes de caches... donc pas du tout terrible en performance (sur intel, il y a de la logique dans le processeur pour invalider les lignes en question automatiquement dans ce contexte, ca n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acceder a de la memoire pas alignee fonctionne, mais rame un peu).
oui tout à fait.
"la solution" serait sans doute similaire a celle utilisee pour les gestionnaires de signaux: a savoir parametrer un peu plus le bout de code genere au vol, de facon a ne plus avoir a le generer au vol, et quitte a se taper un petit bout d'indirection supplementaire, a finalement appeler du code standard, qui ne foutra pas le bordel dans le cache...
Est-ce que ca signifie changer la convention d'appel des fonctions C en général pour non seulement passer un pointeur sur le code, mais en plus un pointeur sur la stack-frame de l'appelant? La notion même de stack-frame pourrait être déplacée de la pile du CPU vers le tas pour n'empiler que ces deux pointeurs en question. En effet de bord ca aiderait à réduire la consommation de pile pour les cpu qui en ont peu (et aussi dans le cadre de multithread massif).
sam.
Marc Espie a écrit :
In article <4c5599dc$0$10129$426a74cc@news.free.fr>,
Samuel DEVULDER <samuel-dot-devulder@laposte-dot-com> wrote:
Possible, je n'ai fait que zieuter au code ASM que GCC générait sur x86.
Ca sent trop l'implementation de type "tiens je suis en train de faire
ma these sur les implementations de clotures, si j'en mettais un bout
dans gcc".
C'est comme ca que ca s'est vraiment passé dans gcc tu penses?
J'aimerais bien connaitre qui a eu cette idée d'implémenter les
fonctions embarquées, leur limitations et la motivation derrière
(callbacks dans des GUI?)
Outre le fait que les systemes modernes n'ont plus la pile en executable,
c'est quand meme de la generation de code au vol, ce qui sur tous les
Oui.. Et c'est ca que je trouve suspect dans ce que j'ai vu du code
asm.. En même temps la fonction embarquée quand i=3 (la vars locale
référencée), n'est pas identique à la fonction quand i=4. Les deux
fonctions peuvent être utilisées simultanément et ne pas se mélanger les
pieds. Ca me semble nécéssiter une notion de génération dynamique de
"quelque chose d'executable" qui capture l'état de la pile du prog à ce
moment là.
En fait chaque référence utilisée de la fonction a potentiellement un
contexte différent. On a donc affaire à plusieurs fonctions distinctes
les unes des autres du point de vu "bas niveau" à chaque référence d'une
telle fonction.
processeurs decents (c'est-a-dire pas intel et ses cradouilleries)
necessite d'invalider des lignes de caches... donc pas du tout terrible
en performance (sur intel, il y a de la logique dans le processeur pour
invalider les lignes en question automatiquement dans ce contexte, ca
n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acceder
a de la memoire pas alignee fonctionne, mais rame un peu).
oui tout à fait.
"la solution" serait sans doute similaire a celle utilisee pour les
gestionnaires de signaux: a savoir parametrer un peu plus le bout de code
genere au vol, de facon a ne plus avoir a le generer au vol, et quitte
a se taper un petit bout d'indirection supplementaire, a finalement
appeler du code standard, qui ne foutra pas le bordel dans le cache...
Est-ce que ca signifie changer la convention d'appel des fonctions C en
général pour non seulement passer un pointeur sur le code, mais en plus
un pointeur sur la stack-frame de l'appelant? La notion même de
stack-frame pourrait être déplacée de la pile du CPU vers le tas pour
n'empiler que ces deux pointeurs en question. En effet de bord ca
aiderait à réduire la consommation de pile pour les cpu qui en ont peu
(et aussi dans le cadre de multithread massif).
Possible, je n'ai fait que zieuter au code ASM que GCC générait sur x86.
Ca sent trop l'implementation de type "tiens je suis en train de faire ma these sur les implementations de clotures, si j'en mettais un bout dans gcc".
C'est comme ca que ca s'est vraiment passé dans gcc tu penses? J'aimerais bien connaitre qui a eu cette idée d'implémenter les fonctions embarquées, leur limitations et la motivation derrière (callbacks dans des GUI?)
Outre le fait que les systemes modernes n'ont plus la pile en executable, c'est quand meme de la generation de code au vol, ce qui sur tous les
Oui.. Et c'est ca que je trouve suspect dans ce que j'ai vu du code asm.. En même temps la fonction embarquée quand i=3 (la vars locale référencée), n'est pas identique à la fonction quand i=4. Les deux fonctions peuvent être utilisées simultanément et ne pas se mélanger les pieds. Ca me semble nécéssiter une notion de génération dynamique de "quelque chose d'executable" qui capture l'état de la pile du prog à ce moment là.
En fait chaque référence utilisée de la fonction a potentiellement un contexte différent. On a donc affaire à plusieurs fonctions distinctes les unes des autres du point de vu "bas niveau" à chaque référence d'une telle fonction.
processeurs decents (c'est-a-dire pas intel et ses cradouilleries) necessite d'invalider des lignes de caches... donc pas du tout terrible en performance (sur intel, il y a de la logique dans le processeur pour invalider les lignes en question automatiquement dans ce contexte, ca n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acceder a de la memoire pas alignee fonctionne, mais rame un peu).
oui tout à fait.
"la solution" serait sans doute similaire a celle utilisee pour les gestionnaires de signaux: a savoir parametrer un peu plus le bout de code genere au vol, de facon a ne plus avoir a le generer au vol, et quitte a se taper un petit bout d'indirection supplementaire, a finalement appeler du code standard, qui ne foutra pas le bordel dans le cache...
Est-ce que ca signifie changer la convention d'appel des fonctions C en général pour non seulement passer un pointeur sur le code, mais en plus un pointeur sur la stack-frame de l'appelant? La notion même de stack-frame pourrait être déplacée de la pile du CPU vers le tas pour n'empiler que ces deux pointeurs en question. En effet de bord ca aiderait à réduire la consommation de pile pour les cpu qui en ont peu (et aussi dans le cadre de multithread massif).
sam.
espie
In article <4c55a950$0$23440$, Samuel DEVULDER wrote:
Est-ce que ca signifie changer la convention d'appel des fonctions C en général pour non seulement passer un pointeur sur le code, mais en plus un pointeur sur la stack-frame de l'appelant? La notion même de stack-frame pourrait être déplacée de la pile du CPU vers le tas pour n'empiler que ces deux pointeurs en question. En effet de bord ca aiderait à réduire la consommation de pile pour les cpu qui en ont peu (et aussi dans le cadre de multithread massif).
Ca peut etre une des raisons pour declarer tes fonctions en static: si ta fonction est purement locale a l'unite de compilation (static, et pas de pointeur de fonction qui s'echappe), le compilo peut en profiter pour s'affranchir de l'ABI de ton processeur, et faire exactement ce qu'il a envie avec les appels a ta fonction. C'est ce que font certains compilos recents.
Une des raisons pour lesquelles ca n'est pas plus repandu que ca est que C a une longue tradition de langage "non fonctionnel", ou le programmeur optimise "a la main" son code (et faut dire qu'il y a quinze ans, la technologie des compilateurs le necessitait), et donc les optimisations globales de ce genre n'ont pas un si fort impact que ca sur le code existant.
In article <4c55a950$0$23440$426a74cc@news.free.fr>,
Samuel DEVULDER <samuel-dot-devulder@laposte-dot-com> wrote:
Est-ce que ca signifie changer la convention d'appel des fonctions C en
général pour non seulement passer un pointeur sur le code, mais en plus
un pointeur sur la stack-frame de l'appelant? La notion même de
stack-frame pourrait être déplacée de la pile du CPU vers le tas pour
n'empiler que ces deux pointeurs en question. En effet de bord ca
aiderait à réduire la consommation de pile pour les cpu qui en ont peu
(et aussi dans le cadre de multithread massif).
Ca peut etre une des raisons pour declarer tes fonctions en static:
si ta fonction est purement locale a l'unite de compilation (static, et pas
de pointeur de fonction qui s'echappe), le compilo peut en profiter pour
s'affranchir de l'ABI de ton processeur, et faire exactement ce qu'il a
envie avec les appels a ta fonction. C'est ce que font certains compilos
recents.
Une des raisons pour lesquelles ca n'est pas plus repandu que ca est que
C a une longue tradition de langage "non fonctionnel", ou le programmeur
optimise "a la main" son code (et faut dire qu'il y a quinze ans, la
technologie des compilateurs le necessitait), et donc les optimisations
globales de ce genre n'ont pas un si fort impact que ca sur le code existant.
In article <4c55a950$0$23440$, Samuel DEVULDER wrote:
Est-ce que ca signifie changer la convention d'appel des fonctions C en général pour non seulement passer un pointeur sur le code, mais en plus un pointeur sur la stack-frame de l'appelant? La notion même de stack-frame pourrait être déplacée de la pile du CPU vers le tas pour n'empiler que ces deux pointeurs en question. En effet de bord ca aiderait à réduire la consommation de pile pour les cpu qui en ont peu (et aussi dans le cadre de multithread massif).
Ca peut etre une des raisons pour declarer tes fonctions en static: si ta fonction est purement locale a l'unite de compilation (static, et pas de pointeur de fonction qui s'echappe), le compilo peut en profiter pour s'affranchir de l'ABI de ton processeur, et faire exactement ce qu'il a envie avec les appels a ta fonction. C'est ce que font certains compilos recents.
Une des raisons pour lesquelles ca n'est pas plus repandu que ca est que C a une longue tradition de langage "non fonctionnel", ou le programmeur optimise "a la main" son code (et faut dire qu'il y a quinze ans, la technologie des compilateurs le necessitait), et donc les optimisations globales de ce genre n'ont pas un si fort impact que ca sur le code existant.
| > Ce hack permet en fait de trimballer artificiellement un contexte dans | > un pointeur de fonction, ce qui peut être assez pratique. | | Ãa fait une closure comme en programmation fonctionnelle quoi.
Comme Pascal.
Erwan David <erwan@rail.eu.org> writes:
[...]
| > Ce hack permet en fait de trimballer artificiellement un contexte dans
| > un pointeur de fonction, ce qui peut être assez pratique.
|
| Ãa fait une closure comme en programmation fonctionnelle quoi.
| > Ce hack permet en fait de trimballer artificiellement un contexte dans | > un pointeur de fonction, ce qui peut être assez pratique. | | Ãa fait une closure comme en programmation fonctionnelle quoi.
| Outre le fait que les systemes modernes n'ont plus la pile en executable, | c'est quand meme de la generation de code au vol, ce qui sur tous les | processeurs decents (c'est-a-dire pas intel et ses cradouilleries) | necessite d'invalider des lignes de caches... donc pas du tout terrible | en performance (sur intel, il y a de la logique dans le processeur pour | invalider les lignes en question automatiquement dans ce contexte, ca | n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acc eder | a de la memoire pas alignee fonctionne, mais rame un peu). | | "la solution" serait sans doute similaire a celle utilisee pour les | gestionnaires de signaux: a savoir parametrer un peu plus le bout de code | genere au vol, de facon a ne plus avoir a le generer au vol, et quitte | a se taper un petit bout d'indirection supplementaire, a finalement | appeler du code standard, qui ne foutra pas le bordel dans le cache...
| Outre le fait que les systemes modernes n'ont plus la pile en executable,
| c'est quand meme de la generation de code au vol, ce qui sur tous les
| processeurs decents (c'est-a-dire pas intel et ses cradouilleries)
| necessite d'invalider des lignes de caches... donc pas du tout terrible
| en performance (sur intel, il y a de la logique dans le processeur pour
| invalider les lignes en question automatiquement dans ce contexte, ca
| n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acc eder
| a de la memoire pas alignee fonctionne, mais rame un peu).
|
| "la solution" serait sans doute similaire a celle utilisee pour les
| gestionnaires de signaux: a savoir parametrer un peu plus le bout de code
| genere au vol, de facon a ne plus avoir a le generer au vol, et quitte
| a se taper un petit bout d'indirection supplementaire, a finalement
| appeler du code standard, qui ne foutra pas le bordel dans le cache...
| Outre le fait que les systemes modernes n'ont plus la pile en executable, | c'est quand meme de la generation de code au vol, ce qui sur tous les | processeurs decents (c'est-a-dire pas intel et ses cradouilleries) | necessite d'invalider des lignes de caches... donc pas du tout terrible | en performance (sur intel, il y a de la logique dans le processeur pour | invalider les lignes en question automatiquement dans ce contexte, ca | n'empeche pas que c'est pas trop bon en perfs, un peu comme le fait d'acc eder | a de la memoire pas alignee fonctionne, mais rame un peu). | | "la solution" serait sans doute similaire a celle utilisee pour les | gestionnaires de signaux: a savoir parametrer un peu plus le bout de code | genere au vol, de facon a ne plus avoir a le generer au vol, et quitte | a se taper un petit bout d'indirection supplementaire, a finalement | appeler du code standard, qui ne foutra pas le bordel dans le cache...