Main () démarre-t-il vraiment un programme C ++?

131

La section $ 3.6.1 / 1 du standard C ++ lit,

Un programme doit contenir une fonction globale appelée main , qui est le début désigné du programme.

Considérons maintenant ce code,

int square(int i) { return i*i; }
int user_main()
{ 
    for ( int i = 0 ; i < 10 ; ++i )
           std::cout << square(i) << endl;
    return 0;
}
int main_ret= user_main();
int main() 
{
        return main_ret;
}

Cet exemple de code fait ce que je compte faire, c'est-à-dire imprimer le carré des nombres entiers de 0 à 9, avant d' entrer dans la main()fonction qui est censée être le "début" du programme.

Je l'ai également compilé avec l' -pedanticoption, GCC 4.5.0. Cela ne donne aucune erreur, pas même d'avertissement!

Donc ma question est,

Ce code est-il vraiment conforme à la norme?

S'il est conforme à la norme, cela n'invalide-t-il pas ce que dit la norme? main()n'est pas le début de ce programme! user_main()exécuté avant le main().

Je comprends que pour initialiser la variable globale main_ret, le use_main()exécute en premier, mais c'est une chose complètement différente; le point est que, il n'annule la déclaration citée $ 3.6.1 / 1 de la norme, comme n'est pas le début du programme; c'est en fait la fin de ce programme!main()


ÉDITER:

Comment définissez-vous le mot «commencer»?

Cela se résume à la définition de l'expression «début du programme» . Alors, comment le définissez-vous exactement?

Nawaz
la source

Réponses:

85

Non, C ++ fait beaucoup de choses pour "définir l'environnement" avant l'appel de main; cependant, main est le début officiel de la partie "spécifiée par l'utilisateur" du programme C ++.

Une partie de la configuration de l'environnement n'est pas contrôlable (comme le code initial pour configurer std :: cout; cependant, une partie de l'environnement est contrôlable comme des blocs globaux statiques (pour l'initialisation des variables globales statiques). Notez que puisque vous n'avez pas plein control avant main, vous n'avez pas un contrôle total sur l'ordre dans lequel les blocs statiques sont initialisés.

Après main, votre code est conceptuellement "totalement en contrôle" du programme, en ce sens que vous pouvez à la fois spécifier les instructions à exécuter et l'ordre dans lequel les exécuter. Le multi-threading peut réorganiser l'ordre d'exécution du code; mais, vous êtes toujours en contrôle avec C ++ parce que vous avez spécifié que des sections de code s'exécutent (éventuellement) dans le désordre.

Edwin Buck
la source
9
+1 pour ceci "Notez que puisque vous n'avez pas le contrôle total avant main, vous n'avez pas le contrôle total sur l'ordre dans lequel les blocs statiques sont initialisés. Après main, votre code est conceptuellement" totalement en contrôle "de le programme, en ce sens que vous pouvez à la fois spécifier les instructions à exécuter et l'ordre dans lequel les exécuter " . Cela me fait également marquer cette réponse comme réponse acceptée ... Je pense que ce sont des points très importants, qui justifient suffisamment main()comme "début du programme"
Nawaz
13
@Nawaz: notez qu'en plus de ne pas avoir de contrôle total sur l'ordre d'initialisation, vous n'avez aucun contrôle sur les erreurs d'initialisation: vous ne pouvez pas intercepter les exceptions à une portée globale.
André Caron
@Nawaz: Qu'est-ce que les blocs globaux statiques? allez-vous s'il vous plaît l'expliquer en utilisant un exemple simple? Merci
Destructor
@meet: Les objets déclarés au niveau de l'espace de noms ont staticune durée de stockage, et en tant que tels, ces objets appartenant à différentes unités de traduction peuvent être initialisés dans n'importe quel ordre (car l'ordre n'est pas spécifié par la norme). Je ne sais pas si cela répond à votre question, même si c'est ce que je pourrais dire dans le contexte de ce sujet.
Nawaz
88

Vous ne lisez pas correctement la phrase.

Un programme doit contenir une fonction globale appelée main, qui est le début désigné du programme.

La norme définit le mot «début» aux fins du reste de la norme. Il ne dit pas qu'aucun code ne s'exécute avant d' mainêtre appelé. Il dit que le début du programme est considéré comme à la fonction main.

Votre programme est conforme. Votre programme n'a "démarré" qu'au démarrage de main. Le constructeur est appelé avant que votre programme "démarre" selon la définition de "start" dans la norme, mais cela n'a guère d'importance. Beaucoup de code est exécuté avant mainest jamais appelé dans tous les programmes, non seulement cet exemple.

Pour les besoins de la discussion, votre code constructeur est exécuté avant le «démarrage» du programme, ce qui est entièrement conforme à la norme.

Adam Davis
la source
3
Désolé, mais je ne suis pas d'accord avec votre interprétation de cet article.
Courses de légèreté en orbite le
Je pense qu'Adam Davis a raison, "main" ressemble plus à une sorte de restriction de codage.
laike9m
@LightnessRacesinOrbit Je n'ai jamais fait de suivi, mais pour moi, cette phrase peut se résumer logiquement à "une fonction globale appelée main est le début désigné du programme" (italiques ajoutés). Quelle est votre interprétation de cette phrase?
Adam Davis
1
@AdamDavis: Je ne me souviens pas de ma préoccupation. Je ne peux pas penser à un maintenant.
Courses de légèreté en orbite
23

Votre programme ne sera pas lié et ne fonctionnera donc pas à moins qu'il n'y ait un fichier main. Cependant main () ne provoque pas le démarrage de l'exécution du programme car les objets au niveau du fichier ont des constructeurs qui s'exécutent au préalable et il serait possible d'écrire un programme entier qui exécute sa durée de vie avant que main () soit atteint et de laisser main lui-même avoir un corps vide.

En réalité, pour appliquer cela, vous devez avoir un objet construit avant main et son constructeur pour appeler tout le flux du programme.

Regarde ça:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

Le flux de votre programme découlerait effectivement de Foo::Foo()

Vache à lait
la source
13
+1. Mais notez que si vous avez plusieurs objets globaux dans différentes unités de traduction, cela vous posera rapidement des problèmes car l'ordre dans lequel les constructeurs sont appelés n'est pas défini. Vous pouvez vous en tirer avec des singletons et une initialisation paresseuse, mais dans un environnement multithread, les choses deviennent très laides rapidement. En un mot, ne faites pas cela en vrai code.
Alexandre C.
3
Alors que vous devriez probablement donner à main () un corps approprié dans votre code et lui permettre d'exécuter l'exécution, le concept d'objets en dehors de ce démarrage est sur lequel reposent de nombreuses bibliothèques LD_PRELOAD.
CashCow
2
@Alex: La norme dit non définie, mais en pratique, l'ordre des liens (généralement, selon le compilateur) contrôle l'ordre d'initiation.
ThomasMcLeod
1
@Thomas: Je n'essaierais sûrement même pas à distance de me fier à ça. Je n'essaierais certainement pas de contrôler manuellement le système de construction.
Alexandre C.
1
@Alex: plus si important, mais à l'époque, nous utilisions l'ordre des liens pour contrôler l'image de construction afin de réduire la pagination de la mémoire physique. Il existe d'autres raisons secondaires pour lesquelles vous souhaiterez peut-être contrôler l'ordre d'initialisation même s'il n'affecte pas la sémantique du programme, comme les tests de comparaison des performances de démarrage.
ThomasMcLeod
15

Vous avez également marqué la question comme "C", alors, en parlant strictement de C, votre initialisation devrait échouer selon la section 6.7.8 "Initialisation" de la norme ISO C99.

La plus pertinente dans ce cas semble être la contrainte # 4 qui dit:

Toutes les expressions dans un initialiseur pour un objet qui a une durée de stockage statique doivent être des expressions constantes ou des littéraux de chaîne.

Donc, la réponse à votre question est que le code n'est pas conforme à la norme C.

Vous voudriez probablement supprimer la balise "C" si vous n'étiez intéressé que par la norme C ++.

Remo.D
la source
4
@ Remo.D pourriez-vous nous dire ce qu'il y a dans cette section. Nous n'avons pas tous la norme C :).
UmmaGumma
2
Puisque vous êtes si pointilleux: Hélas, ANSI C est obsolète depuis 1989. ISO C90 ou C99 sont les normes pertinentes à citer.
Lundin
@Lundin: Personne n'est jamais assez pointilleux :) Je lisais ISO C99 mais je suis assez confiant que cela s'applique aussi à C90.
Remo.D
@Un coup de feu. Tu as raison, a ajouté la phrase qui me semble la plus pertinente ici.
Remo.D
3
@Remo: +1 pour fournir des informations indiquant que ce n'est pas valide C; je ne savais pas ça. Voilà comment les gens apprennent, parfois par plan, parfois par hasard!
Nawaz
10

La section 3.6 dans son ensemble est très claire sur l'interaction mainet les initialisations dynamiques. Le «début désigné du programme» n'est utilisé nulle part ailleurs et est simplement descriptif de l'intention générale de main(). Cela n'a aucun sens d'interpréter cette expression d'une manière normative qui contredit les exigences plus détaillées et plus claires de la Norme.

aschepler
la source
9

Le compilateur doit souvent ajouter du code avant main () pour être conforme au standard. Parce que la norme spécifie que l'initalisation des globals / statiques doit être effectuée avant l'exécution du programme. Et comme mentionné, il en va de même pour les constructeurs d'objets placés à portée de fichier (globals).

Ainsi, la question initiale est pertinente pour C, car dans un programme C vous auriez toujours l'initialisation globale / statique à faire avant que le programme puisse être démarré.

Les standards supposent que ces variables sont initialisées par "magie", car elles ne disent pas comment elles doivent être définies avant l'initialisation du programme. Je pense qu'ils considéraient cela comme quelque chose qui sortait du cadre d'une norme de langage de programmation.

Edit: Voir par exemple ISO 9899: 1999 5.1.2:

Tous les objets avec une durée de stockage statique doivent être initialisés (mis à leurs valeurs initiales) avant le démarrage du programme. La manière et le moment de cette initialisation ne sont par ailleurs pas spécifiés.

La théorie derrière la façon dont cette "magie" devait être faite remonte à la naissance de C, quand c'était un langage de programmation destiné à être utilisé uniquement pour le système d'exploitation UNIX, sur des ordinateurs basés sur la RAM. En théorie, le programme serait capable de charger toutes les données pré-initialisées du fichier exécutable dans la RAM, en même temps que le programme lui-même était téléchargé dans la RAM.

Depuis lors, les ordinateurs et les systèmes d'exploitation ont évolué et C est utilisé dans un domaine beaucoup plus large que prévu à l'origine. Un système d'exploitation PC moderne a des adresses virtuelles, etc., et tous les systèmes embarqués exécutent du code à partir de la ROM, pas de la RAM. Il existe donc de nombreuses situations dans lesquelles la RAM ne peut pas être réglée "automatiquement".

De plus, le standard est trop abstrait pour savoir quoi que ce soit sur les piles et la mémoire de processus, etc. Ces choses doivent être faites aussi, avant que le programme ne démarre.

Par conséquent, pratiquement tous les programmes C / C ++ ont du code init / "copy-down" qui est exécuté avant l'appel de main, afin de se conformer aux règles d'initialisation des standards.

Par exemple, les systèmes embarqués ont généralement une option appelée "démarrage non conforme ISO" où toute la phase d'initialisation est ignorée pour des raisons de performances, puis le code démarre en fait directement à partir de main. Mais ces systèmes ne sont pas conformes aux normes, car vous ne pouvez pas vous fier aux valeurs init des variables globales / statiques.

Lundin
la source
4

Votre "programme" renvoie simplement une valeur d'une variable globale. Tout le reste est du code d'initialisation. Ainsi, la norme tient - vous avez juste un programme très simple et une initialisation plus complexe.

Zac Howland
la source
2

Cela ressemble à un chipotage sémantique anglais. L'OP fait d'abord référence à son bloc de code comme "code" et plus tard comme "programme". L'utilisateur écrit le code, puis le compilateur écrit le programme.

dSerk
la source
1

main est appelée après l'initialisation de toutes les variables globales.

Ce que la norme ne spécifie pas, c'est l'ordre d'initialisation de toutes les variables globales de tous les modules et bibliothèques liées statiquement.

vz0
la source
0

Oui, main est le "point d'entrée" de chaque programme C ++, à l'exception des extensions spécifiques à l'implémentation. Même ainsi, certaines choses se produisent avant main, notamment l'initialisation globale comme pour main_ret.

Fred Nurk
la source