web-dev-qa-db-fra.com

Ce code C obscurci prétend fonctionner sans main (), mais que fait-il vraiment?

#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?? ");
}

Est-ce que ceci appelle indirectement main? Comment?

85
Rajeev Singh

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 une fonction de démarrage de programme d’environnement autonome , la définition d’implémentation peut être définie alors que dans un environnement hébergé , elle devrait être main. Aucun programme en C ne peut s'exécuter sans une fonction de démarrage de programme dans les environnements définis.

Dans votre cas, main est masqué par les définitions du préprocesseur. begin() sera étendu à decode(a,n,i,m,a,t,e), lequel sera étendu à 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 4th, 1st, 3rd et 2dakota du Nord paramètre utilisé dans la liste de remplacement.

s, t, u, m, p, e, d
1  2  3  4  5  6  7

Les autres ne servent à rien ( seulement pour masquer ). L'argument passé à decode est "a, n, i, m, a, t Ainsi, les identifiants m, s, u et t sont remplacés par les arguments m, a, i et n, respectivement.

 m --> m  
 s --> a 
 u --> i 
 t --> n
193
haccks

Essayez d’utiliser gcc -E source.c, La sortie se termine par:

int main()
{
    printf("Ha HA see how it is?? ");
}

Ainsi, une fonction main() est générée par le préprocesseur.

72
jdarthenay

Le programme en question appelle en raison de l'expansion des macros main(), mais votre hypothèse est erronée - il ne doit pas appeler du tout main()!

Strictement parlant, vous pouvez avoir un programme C et pouvoir le compiler sans avoir le symbole main. main est quelque chose que le c library s'attend à entrer dans, après avoir terminé sa propre initialisation. Habituellement, vous sautez dans main à partir du symbole libc appelé _start. Il est toujours possible d'avoir un programme très valide, qui exécute simplement 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 le voir imprimer Hello World! à l'écran en émettant simplement des appels système (interruptions) dans l'assemblage en ligne.

Pour plus d'informations sur ce problème particulier, consultez le blog de ksplice

Un autre problème intéressant est que vous pouvez également avoir un programme qui compile sans que le symbole main ne corresponde à une fonction C. Par exemple, vous pouvez avoir les éléments suivants comme programme C très valide, qui ne fait que gémir le compilateur lorsque vous atteignez le niveau Warnings.

/* 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 correspondant 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 coup d'œil à ceci article de blog , et c'est là que je l'ai également lu en premier.

Je veux faire un dernier avis au sujet de ces programmes. Je ne sais pas s'ils s'inscrivent comme programmes C valides conformément à 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.

37
NlightNFotis

Quelqu'un essaie d'agir comme un magicien. Il pense qu'il peut nous tromper. Mais nous savons tous que c l’exécution du programme commence par main().

La int begin() sera remplacée par decode(a,n,i,m,a,t,e) par un passage d'étage de préprocesseur. Encore une fois, decode(a,n,i,m,a,t,e) sera remplacé par m ## a ## i ## n. Comme par association positionnelle d’appel de macro, s aura une valeur de caractère a. De même, u sera remplacé par 'i' et t sera remplacé par 'n'. Et c'est comme ça, m##s##u##t deviendra main

En ce qui concerne, ## symbole en expansion de macro, il s’agit de l’opérateur de prétraitement et il effectue le collage de jetons. Lorsqu'une macro est développée, les deux jetons situés de part et d'autre de chaque opérateur "##" sont combinés en un seul jeton, qui remplace alors le "##" et les deux jetons d'origine dans le développement de la macro.

Si vous ne me croyez pas, vous pouvez compiler votre code avec -E drapeau. Cela 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
30
abhiarora

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 la macro begin est définie.

Par conséquent, la macro begin est simplement définie comme étant main.

11
Frxstrem

Dans votre exemple, la fonction main() est réellement présente, car begin est une macro que le compilateur remplace par la macro decode qui à son tour a été remplacée par l'expression m ## s # #Utah. En utilisant l’extension de macro ##, Vous obtiendrez le mot main à partir de decode. C'est une trace:

begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main

main() est une astuce, mais utiliser le nom main() pour 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 en tant qu'un de ses outils.

Sous Windows, vous n'utilisez pas toujours main(), mais plutôt WinMain ou wWinMain , bien que vous pouvez 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, qui définit le point d'entrée et non le langage lui-même. Vous pouvez même définir notre propre point d’entrée et créer une bibliothèque également exécutable !

2
Ho1