Comment est-il possible de ne rien déclarer dans main () en C ++ et d'avoir une application fonctionnelle après la compilation?

86

Dans une interview, j'ai été confronté à une question comme celle-ci:

Votre ami vous a donné un seul fichier de code source qui imprime les numéros de Fibonacci sur la console. Notez que le bloc main () est vide et ne contient aucune instruction.

Expliquez comment cela est possible (indice: instance globale!)

Je veux vraiment savoir à ce sujet, comment une telle chose peut même être possible!

Rika
la source
26
Regardez l'indice!
R. Martinho Fernandes
14
Parce que c'est quelque chose dont 1) je n'avais pas entendu parler, 2) est des anecdotes utiles parce que les gens le demandent dans les interviews, 3) une application intéressante de la langue à connaître pour que 4) je puisse la reconnaître et poignarder n'importe qui au visage avec un shiv rouillé si je les vois l'utiliser réellement dans le code de production.
OmnipotentEntity
4
Un programmeur C ++ professionnel et compétent connaîtra la réponse à cette question. Si le but de cette question d' entretien est de déterminer si la personne interrogée est un programmeur C ++ professionnel et compétent, alors la question ne devrait pas lui donner la réponse.
John Dibling
1
Dans le cadre d'une interview, une alternative serait d'avoir la logique à l'intérieur de n'importe quelle fonction du code et d'enregistrer la sortie en utilisant assertou #pragma messageetc. Cela redirigera la sortie vers la console pendant la compilation. Le programme peut même ne jamais être entièrement compilé, mais c'est certainement une façon amusante de montrer votre réflexion «hors de la boîte» pendant l'entretien. Cela satisfait la question citée car il ne mentionne rien au sujet du binaire généré; il parle plutôt d'un fichier C qui peut afficher des "trucs" sur la console. ;-)
TheCodeArtist
1
Était-ce une interview pour IOCC ? :-) Ok, j'avoue que je le fais souvent pour initialiser mes usines ou exécuter du code de test. Btw, « fichier de code source unique » est également un indice, que l'entrée-pint (main par défaut) n'est pas remplacé par l'éditeur de liens.
Valentin Heinitz

Réponses:

127

Il est très probablement implémenté comme (ou une variante de celui-ci):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

Dans ce code, la variable globale ignoredoit être initialisée avant d'entrer en main()fonction. Maintenant, pour initialiser le global, print_fibs()doit être exécuté là où vous pouvez faire n'importe quoi - dans ce cas, calculer les nombres de fibonacci et les imprimer! Une chose similaire que j'ai montrée dans la question suivante (que j'avais posée il y a longtemps):

Notez qu'un tel code n'est pas sûr et doit être évité en général. Par exemple, l' std::coutobjet peut ne pas être initialisé lorsqu'il print_fibs()est exécuté, si oui, que ferait std::cout-il dans la fonction? Cependant, si dans d'autres circonstances, cela ne dépend pas d'un tel ordre d'initialisation, alors il est prudent d'appeler des fonctions d'initialisation (ce qui est une pratique courante en C et C ++).

Nawaz
la source
3
@Nawaz Il vaut probablement la peine de citer les garanties exactes. Les objets d'une unité de traduction sont garantis d'être initialisés dans l'ordre. Les objets de flux standard sont garantis d'être initialisés avant ou pendant la première initialisation d'un std::ios_base::Initobjet. Et <iostream>est garanti de se comporter "comme si" il contenait une instance d'un std::ios_base_Initobjet à la portée de l'espace de noms.
James Kanze
3
@ Steve314: Il ne renvoie rien, c'est pourquoi j'ai utilisé l'opérateur virgule, pour m'assurer que le type de l'expression entière (print_fibs(), 0)est int. Voici la démo en ligne .
Nawaz
1
@Nawaz Une alternative à la fonction void et à l'opérateur virgule serait de renvoyer a bool, et la variable bool fibsPrinted. C'est probablement un peu plus propre si la fonction ne sert qu'ici. (Mais la différence n'est probablement pas suffisante pour s'inquiéter.)
James Kanze
1
+1, Parlez de génial. J'ai dû rejoindre stackoverflow juste pour voter pour cette question et cette réponse.
Point fixe
1
@Nawaz Je ne suis pas sûr de votre point de vue. La définition de std::coutest quelque part dans la bibliothèque. Mais comme je l'ai déjà souligné, la norme exige qu'il soit initialisé avant que le premier constructeur d'un std::ios_base::Initobjet ne soit terminé, et il exige que notamment <iostream>se comporte comme si un std::ios_base::Initobjet était défini à la portée de l'espace de noms. Si l'unité de traduction inclut <iostream>avant la définition de l'objet en cours d'initialisation, la construction std::coutest garantie.
James Kanze
18

J'espère que cela t'aides

class cls
{
  public:
    cls()
    {
      // Your code for fibonacci series
    }
} objCls;

int main()
{
}

Ainsi, dès qu'une variable globale de la classe est déclarée, le constructeur est appelé et là vous ajoutez la logique pour imprimer la série de Fibonacci.

Saksham
la source
9

Oui c'est possible. Vous devez déclarer une instance globale d'un objet qui calcule les nombres de Fibonacci dans le constructeur d'objet.

Monsieur Beer
la source
6
Vous devez déclarer une instance globale d'un objet dont l'initialiseur calcule les nombres de Fibonacci.
James Kanze
4

Je connais quelques exemples comme celui que vous racontez. Une façon de l'obtenir consiste à utiliser la métaprogrammation du modèle. En l'utilisant, vous pouvez déplacer un processus de calcul vers la compilation.

Ici vous pouvez obtenir un exemple avec les nombres de Fibonacci

Si vous l'utilisez dans un constructeur de classe statique et que vous pouvez écrire les nombres sans avoir besoin d'écrire de code dans la fonction principale.

J'espère que cela vous aide.

superarce
la source
3

Des choses peuvent se produire lors de l'initialisation des variables globales / statiques. Le code sera déclenché au démarrage de l'application.

log0
la source
3

Tous les constructeurs [*] pour les objets de portée fichier sont appelés avant d'atteindre main, comme toutes les expressions d'initialisation pour les variables de portée fichier non objet.

Edit: De plus, tous les destructeurs [*] pour tous les objets de portée fichier sont appelés dans l'ordre inverse de la construction après les mainsorties. Vous pourriez, en théorie, mettre votre programme fibonacci dans le destructeur d'un objet.

[*] Notez que «tous» ignore le comportement de chargement et de déchargement dynamique des bibliothèques avec lesquelles votre programme n'était pas directement lié. Cependant, techniquement, ceux-ci sont en dehors du langage C ++ de base.

Joe Z
la source
Tout ? Même ceux dans les DLL qui sont explicitement chargés après main?
James Kanze
Eh bien, C ++ ne définit pas techniquement les bibliothèques chargées dynamiquement, donc dans le C ++ pur, ma déclaration est correcte. Alors, omettez-le "Tout, sauf pour les initialiseurs et les objets de portée fichier contenus dans les DLL / DSO chargés après avoir atteint main." Dans ce cas, mainest vide, donc ces DLL / DSO devraient être chargées par des destructeurs, ce qui est sacrément pervers. Mais, étant de l'informatique, je suppose que nous devrions être prudents avec des mots comme «tous».
Joe Z
J'ai ajouté une mise en garde sur «tous» à ma réponse ci-dessus, et j'ai également ajouté une note sur les détecteurs.
Joe Z
Oui, mais j'espère que cela viendra. Le pré C ++ 11 contenait un libellé weasel destiné à autoriser les DLL, mais qui en pratique signifiait seulement que techniquement, la garantie n'était pas toujours là, même si elle était dans toutes les implémentations réelles, et une grande partie du code en dépendait. C ++ 11 a corrigé cela, au moins.
James Kanze