#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Cela appelle-t-il indirectement main
? Comment?
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Cela appelle-t-il indirectement main
? Comment?
Réponses:
Le langage C définit l'environnement d'exécution en deux catégories: autonome et hébergé . Dans les deux environnements d'exécution, une fonction est appelée par l'environnement pour le démarrage du programme.
Dans un environnement autonome, la fonction de démarrage du programme peut être définie par l'implémentation alors que dans un environnement hébergé , elle devrait l'être main
. Aucun programme en C ne peut s'exécuter sans la fonction de démarrage du programme sur les environnements définis.
Dans votre cas, main
est masqué par les définitions du préprocesseur. begin()
s'étendra à decode(a,n,i,m,a,t,e)
laquelle sera étendu davantage main
.
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main()
decode(s,t,u,m,p,e,d)
est une macro paramétrée avec 7 paramètres. La liste de remplacement pour cette macro est m##s##u##t
. m, s, u
et t
sont 4 e , 1 er , 3 e et 2 e paramètre utilisé dans la liste de remplacement.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
Le repos n'est d'aucune utilité ( juste pour obscurcir ). L'argument passé à decode
est " a , n , i , m , a, t, e" donc, les identificateurs m, s, u
et t
sont remplacés par des arguments m, a, i
et n
, respectivement.
m --> m
s --> a
u --> i
t --> n
_start()
. Ou encore plus bas niveau, je peux essayer d'aligner simplement le début de mon programme avec l'adresse à laquelle l'adresse IP est définie après le démarrage. main()
est la bibliothèque C Standard . C lui-même n'impose aucune restriction à ce sujet.
decode(a,n,i,m,a,t,e)
devenir m##a##i##n
? Remplace-t-il les caractères? Pouvez-vous fournir un lien vers la documentation de la decode
fonction? Merci.
begin
est défini pour être remplacé par decode(a,n,i,m,a,t,e)
ce qui est défini avant. Cette fonction prend les arguments s,t,u,m,p,e,d
et les concatène sous cette forme m##s##u##t
( ##
signifie concaténer). C'est-à-dire qu'il ignore les valeurs de p, e et d. Lorsque vous «appelez» decode
avec s = a, t = n, u = i, m = m, il remplace effectivement begin
par main
.
Essayez d'utiliser gcc -E source.c
, la sortie se termine par:
int main()
{
printf("Ha HA see how it is?? ");
}
Ainsi, une main()
fonction est en fait générée par le préprocesseur.
Le programme en question ne appel en main()
raison de l' expansion macro, mais votre hypothèse est erronée - il n'a pas dû appeler main()
du tout!
À proprement parler, vous pouvez avoir un programme C et être capable de le compiler sans avoir de main
symbole. main
est quelque chose auquel le c library
s'attend à sauter, après avoir terminé sa propre initialisation. Habituellement, vous sautez à main
partir du symbole libc connu sous le nom de _start
. Il est toujours possible d'avoir un programme très valide, qui exécute simplement l'assembly, sans avoir de main. Regarde ça:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
Compilez ce qui précède avec gcc -nostdlib without_main.c
et voyez-le imprimerHello World!
à l'écran simplement en émettant des appels système (interruptions) dans l'assemblage en ligne.
Pour plus d'informations sur ce problème particulier, consultez le blog ksplice
Un autre problème intéressant est que vous pouvez également avoir un programme qui compile sans que le main
symbole corresponde à une fonction C. Par exemple, vous pouvez avoir ce qui suit comme programme C très valide, qui ne fait gémir le compilateur que lorsque vous augmentez le niveau d'avertissement.
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
Les valeurs du tableau sont des octets qui correspondent aux instructions nécessaires pour imprimer Hello World à l'écran. Pour un compte rendu plus détaillé du fonctionnement de ce programme spécifique, jetez un œil à ce billet de blog , où je l'ai également lu en premier.
Je veux faire un dernier avis sur ces programmes. Je ne sais pas s'ils s'enregistrent en tant que programmes C valides selon la spécification du langage C, mais les compiler et les exécuter est certainement très possible, même s'ils enfreignent la spécification elle-même.
_start
partie d'une norme définie ou est-ce uniquement spécifique à la mise en œuvre? Votre "main en tant que tableau" est certainement spécifique à l'architecture. Également important, il ne serait pas déraisonnable que votre astuce "main en tant que tableau" échoue au moment de l'exécution en raison de restrictions de sécurité (bien que cela soit plus probable si vous n'utilisiez pas le const
qualificatif, et que de nombreux systèmes le permettraient).
_start
n'est pas dans le standard ELF, bien que le psABI AMD64 contienne une référence _start
à 3.4 Process Initialization . Officiellement, ELF ne connaît que l'adresse à e_entry
dans l'en-tête ELF, _start
c'est juste un nom que l'implémentation a choisi.
const
peu importe un peu - le nom du symbole dans ce fichier exécutable binaire est main
. Ni plus ni moins. const
est une construction C qui ne veut rien dire au moment de l'exécution.
Quelqu'un essaie d'agir comme un magicien. Il pense qu'il peut nous tromper. Mais nous le savons tous, l'exécution du programme c commence par main()
.
Le int begin()
sera remplacé decode(a,n,i,m,a,t,e)
par un passage d'étape de préprocesseur. Là encore, decode(a,n,i,m,a,t,e)
sera remplacé par m ## a ## i ## n. Comme par association positionnelle d'appel de macro, la s
volonté a une valeur de caractère a
. De même, u
sera remplacé par «i» et t
sera remplacé par «n». Et c'est ainsi m##s##u##t
que deviendramain
En ce qui concerne le ##
symbole dans l'expansion de macro, c'est l'opérateur de prétraitement et il effectue le collage de jetons. Lorsqu'une macro est développée, les deux jetons de chaque côté de chaque opérateur '##' sont combinés en un seul jeton, qui remplace alors '##' et les deux jetons d'origine dans l'expansion de la macro.
Si vous ne me croyez pas, vous pouvez compiler votre code avec -E
flag. Il arrêtera le processus de compilation après le prétraitement et vous pourrez voir le résultat du collage de jetons.
gcc -E FILENAME.c
decode(a,b,c,d,[...])
mélange les quatre premiers arguments et les joint pour obtenir un nouvel identifiant, dans l'ordre dacb
. (Les trois arguments restants sont ignorés.) Par exemple, decode(a,n,i,m,[...])
donne l'identifiant main
. Notez que c'est ce que lebegin
macro est définie.
Par conséquent, la begin
macro est simplement définie comme main
.
Dans votre exemple, la main()
fonction est réellement présente, car il begin
s'agit d'une macro que le compilateur remplace par une decode
macro qui à son tour est remplacée par l'expression m ## s ## u ## t. En utilisant l'expansion macro ##
, vous atteindrez le mot à main
partir de decode
. Ceci est une trace:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
C'est juste une astuce à avoir main()
, mais l'utilisation du nom main()
de la fonction d'entrée du programme n'est pas nécessaire en langage de programmation C. Cela dépend de vos systèmes d'exploitation et de l'éditeur de liens comme l'un de ses outils.
Sous Windows, vous n'utilisez pas toujours main()
, mais plutôt WinMain
ouwWinMain
, bien que vous puissiez utiliser main()
, même avec la chaîne d'outils de Microsoft . Sous Linux, on peut utiliser _start
.
C'est à l'éditeur de liens en tant qu'outil du système d'exploitation de définir le point d'entrée, et non la langue elle-même. Vous pouvez même définir notre propre point d'entrée, et vous pouvez créer une bibliothèque qui est également exécutable !
main()
fonction au langage de programmation C, ce qui n'est pas correct.