web-dev-qa-db-fra.com

Pourquoi déclarer main en tant que tableau se compile-t-il?

J'ai vu n extrait de code sur CodeGolf qui est conçu comme une bombe de compilation, où main est déclaré comme un énorme tableau. J'ai essayé la version (non-bombe) suivante:

int main[1] = { 0 };

Il semble bien compiler sous Clang et avec seulement un avertissement sous GCC:

avertissement: 'main' est généralement une fonction [-Wmain]

Le binaire résultant est, bien sûr, des ordures.

Mais pourquoi compile-t-il du tout? Est-ce même autorisé par la spécification C? La section que je pense pertinente est la suivante:

5.1.2.2.1 Démarrage du programme

La fonction appelée au démarrage du programme est nommée main. L'implémentation ne déclare aucun prototype pour cette fonction. Il doit être défini avec un type de retour int et sans paramètre [...] ou avec deux paramètres [...] ou d'une autre manière définie par l'implémentation.

"Une autre manière définie par l'implémentation" inclut-elle un tableau global? (Il me semble que la spécification fait toujours référence à une fonction .)

Sinon, s'agit-il d'une extension du compilateur? Ou une fonctionnalité des chaînes d'outils, qui sert un autre objectif et ils ont décidé de la rendre disponible via le frontend?

56

C'est parce que C permet un environnement "non hébergé" ou autonome qui ne nécessite pas la fonction main. Cela signifie que le nom main est libéré pour d'autres utilisations. C'est pourquoi le langage en tant que tel permet de telles déclarations. La plupart des compilateurs sont conçus pour prendre en charge les deux (la différence réside principalement dans la façon dont la liaison est effectuée) et, par conséquent, ils n'interdisent pas les constructions qui seraient illégales dans un environnement hébergé.

La section à laquelle vous vous référez dans la norme se réfère à l'environnement hébergé, la correspondante pour l'autoportant est:

dans un environnement autonome (dans lequel l'exécution du programme C peut avoir lieu sans aucun avantage d'un système d'exploitation), le nom et le type de la fonction appelée au démarrage du programme sont définis par l'implémentation. Toutes les installations de bibliothèque disponibles pour un programme indépendant, autres que l'ensemble minimal requis par l'article 4, sont définies par l'implémentation.

Si vous le liez ensuite comme d'habitude, cela ira mal puisque l'éditeur de liens a normalement peu de connaissances sur la nature des symboles (quel type il a ou même s'il s'agit d'une fonction ou d'une variable). Dans ce cas, l'éditeur de liens résoudra avec plaisir les appels à main vers la variable nommée main. Si le symbole n'est pas trouvé, cela entraînera une erreur de lien.

Si vous le liez comme d'habitude, vous essayez essentiellement d'utiliser le compilateur en fonctionnement hébergé et de ne pas définir main comme vous êtes censé signifier un comportement non défini conformément à l'annexe J.2:

le comportement n'est pas défini dans les circonstances suivantes:

  • ...
  • programme dans un environnement hébergé ne définit pas une fonction nommée main en utilisant l'un des formulaires spécifiés (5.1.2.2.1)

Le but de la possibilité indépendante est de pouvoir utiliser C dans des environnements où (par exemple) les bibliothèques standard ou l'initialisation CRT ne sont pas données. Cela signifie que le code qui est exécuté avant que main soit appelé (c'est l'initialisation CRT qui initialise le runtime C) peut ne pas être fourni et que vous seriez censé le fournir vous-même (et vous pouvez décider d'avoir un main ou peut décider de ne pas le faire).

38
skyking

Si vous souhaitez savoir comment créer un programme dans le tableau principal: https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html . L'exemple de source contient juste un tableau char (et plus tard int) appelé main qui est rempli d'instructions machine.

Les principales étapes et problèmes étaient les suivants:

  • Obtenez les instructions machine d'une fonction principale à partir d'un vidage de mémoire gdb et copiez-la dans le tableau
  • Balisez les données dans main[] exécutable en le déclarant const (les données sont soit en écriture soit en exécutable)
  • Dernier détail: modifier une adresse pour les données de chaîne réelles.

Le code C résultant est juste

const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};

mais se traduit par un programme exécutable sur un PC 64 bits:

$ gcc -Wall final_array.c -o sixth
final_array.c:1:11: warning: ‘main’ is usually a function [-Wmain]
 const int main[] = {
           ^
$ ./sixth 
Hello World!
24
tymmej

Le problème est que main n'est pas un identifiant réservé. La norme C indique seulement que dans les systèmes hébergés, il existe généralement une fonction appelée main. Mais rien dans la norme ne vous empêche d'abuser du même identifiant à d'autres fins sinistres.

GCC vous donne un avertissement suffisant "main est généralement une fonction", laissant entendre que l'utilisation de l'identifiant main à d'autres fins non liées n'est pas une idée brillante.


Exemple idiot:

#include <stdio.h>

int main (void)
{
  int main = 5;
  main:

  printf("%d\n", main);
  main--;

  if(main)
  {
    goto main;
  }
  else
  {
    int main (void);
    main();
  }
}

Ce programme imprimera à plusieurs reprises les nombres 5,4,3,2,1 jusqu'à ce qu'il obtienne un débordement de pile et se bloque (n'essayez pas cela à la maison). Malheureusement, le programme ci-dessus est un programme C strictement conforme et le compilateur ne peut pas vous empêcher de l'écrire.

9
Lundin

main n'est - après la compilation - qu'un autre symbole dans un fichier objet comme beaucoup d'autres (fonctions globales, variables globales, etc.).

L'éditeur de liens liera le symbole main quel que soit son type. En effet, l'éditeur de liens ne peut pas voir du tout le type du symbole (il peut voir, qu'il n'est pas dans le .text- section cependant, mais il s'en fiche;))

En utilisant gcc, le point d'entrée standard est _start, qui à son tour appelle main () après avoir préparé l'environnement d'exécution. Il sautera donc à l'adresse du tableau d'entiers, ce qui entraînera généralement une mauvaise instruction, une erreur de segmentation ou un autre mauvais comportement.

Tout cela n'a bien sûr rien à voir avec la norme C.

8
Ctx

Il compile uniquement parce que vous n'utilisez pas les options appropriées (et fonctionne parce que les éditeurs de liens ne prennent parfois en compte que les noms des symboles, pas leur type).

$ gcc -std=c89 -pedantic -Wall x.c
x.c:1:5: warning: ISO C forbids zero-size array ‘main’ [-Wpedantic]
 int main[0];
     ^
x.c:1:5: warning: ‘main’ is usually a function [-Wmain]
3
Jens
const int main[1] = { 0xc3c3c3c3 };

Cela se compile et s'exécute sur x86_64 ... ne fait rien simplement retourner: D

1
Zibri