Как можно ничего не объявлять внутри main () в C ++ и при этом иметь работающее приложение после компиляции?

86

В интервью я столкнулся с таким вопросом:

Ваш друг дал вам единственный файл исходного кода, который печатает числа Фибоначчи на консоли. Обратите внимание, что блок main () пуст и внутри него нет операторов.

Объясните, как это возможно (подсказка: глобальный экземпляр!)

Я действительно хочу знать об этом, как такое вообще возможно!

Рика
источник
26
Посмотри на подсказку!
R. Martinho Fernandes
14
Потому что это то, о чем 1) я не слышал, 2) полезная мелочь, потому что люди спрашивают об этом в интервью, 3) интересное приложение языка, которое нужно знать, чтобы 4) я мог распознать его и нанести удар любому человеку в лицо ржавая заточка, если я увижу, что они действительно используют ее в производственном коде.
OmnipotentEntity
4
Грамотный, профессиональный программист на C ++ знает ответ на этот вопрос. Если цель этого вопроса собеседования - определить, является ли собеседник компетентным, профессиональным программистом на C ++, то этот вопрос не должен давать ему ответ.
Джон Диблинг 08
1
В настройках собеседования одной альтернативой было бы иметь логику внутри любой функции в коде и регистрировать вывод с помощью assertили #pragma messageи т. Д. Это перенаправит вывод на консоль во время компиляции. Программа может никогда не скомпилироваться полностью, но это наверняка интересный способ продемонстрировать свое "нестандартное" мышление во время собеседования. Это удовлетворяет процитированный вопрос, так как ничего не упоминается о создании двоичного файла; скорее, это просто говорит о файле C, который может отображать "материал" на консоли. ;-)
TheCodeArtist
1
Это было интервью для IOCC ? :-) Хорошо, признаю, что часто делаю это для инициализации своих фабрик или выполнения некоторого тестового кода. Кстати, « файл с одним исходным кодом» также является намеком на то, что пинта записи (по умолчанию main) не заменяется компоновщиком.
Валентин Хайниц

Ответы:

127

Скорее всего, это реализовано как (или его вариант):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

В этом коде глобальная переменная ignoreдолжна быть инициализирована перед входом в main()функцию. Теперь, чтобы инициализировать глобал, print_fibs()необходимо выполнить все, что угодно - в данном случае вычислить числа Фибоначчи и распечатать их! Похожую вещь я показал в следующем вопросе (который я задавал давно):

Обратите внимание, что такой код небезопасен, и его лучше всего избегать. Например, std::coutобъект может не инициализироваться при print_fibs()выполнении, если да, то что будет std::coutделать в функции? Однако, если в других обстоятельствах это не зависит от такого порядка инициализации, можно безопасно вызывать функции инициализации (что является обычной практикой в ​​C и C ++).

Наваз
источник
3
@Nawaz Наверное, стоит привести точные гарантии. Гарантируется, что объекты в единице трансляции будут инициализированы по порядку. Стандартные объекты потока гарантированно инициализируются до или во время первой инициализации std::ios_base::Initобъекта. И <iostream>гарантированно ведет себя так, как если бы он содержал экземпляр std::ios_base_Initобъекта в области пространства имен.
Джеймс Канце
3
@ Steve314: Он ничего не возвращает, поэтому я использовал оператор запятой, чтобы убедиться, что тип всего выражения (print_fibs(), 0)равен int. Вот онлайн-демо .
Nawaz
1
@Nawaz Альтернативой функции void и оператору запятой было бы возвращение a boolи переменной bool fibsPrinted. Вероятно, это будет немного чище, если функция будет работать только здесь. (Но разницы, вероятно, недостаточно, чтобы о ней беспокоиться.)
Джеймс Канце
1
+1, Разговор об офигенном. Пришлось присоединиться к stackoverflow, чтобы проголосовать за этот вопрос и этот ответ.
Фиксированная точка
1
@Nawaz Я не уверен, о чем вы говорите. Определение std::coutнаходится где-то в библиотеке. Но, как я уже отмечал, стандарт требует, чтобы он был инициализирован до завершения работы первого конструктора std::ios_base::Initобъекта, и требует, чтобы включение <iostream>велось так, как если бы std::ios_base::Initобъект был определен в области пространства имен. Если единица трансляции включает <iostream>до определения инициализируемого объекта, то std::coutсоздание гарантированно будет.
Джеймс Канце
18

Надеюсь это поможет

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

int main()
{
}

Итак, как только глобальная переменная класса объявляется, вызывается конструктор, и вы добавляете туда логику для распечатки ряда Фибоначчи.

Сакшам
источник
9

Да, это возможно. Вам необходимо объявить глобальный экземпляр объекта, который вычисляет числа Фибоначчи в конструкторе объекта.

Мистер пиво
источник
6
Вам необходимо объявить глобальный экземпляр объекта, инициализатор которого вычисляет числа Фибоначчи.
Джеймс Канце
4

Я знаю несколько примеров, подобных тем, что вы рассказываете. Один из способов получить это - использовать метапрограммирование шаблона. С его помощью вы можете перенести некоторый вычислительный процесс в компиляцию.

Здесь вы можете получить пример с числами Фибоначчи.

Если вы используете его в конструкторе статического класса и можете писать числа без необходимости писать какой-либо код в основной функции.

Надеюсь, это поможет тебе.

сверхусилия
источник
3

Что-то может случиться во время инициализации глобальных / статических переменных. Код сработает при запуске приложения.

log0
источник
3

Все [*] конструкторы для объектов файловой области вызываются перед достижением main, как и все выражения инициализатора для переменных файловой области, не являющихся объектами.

Изменить: Кроме того, все [*] деструкторы для всех объектов файловой области вызываются в обратном порядке построения после mainвыхода. Теоретически вы можете поместить свою программу Фибоначчи в деструктор объекта.

[*] Обратите внимание, что 'all' игнорирует поведение динамической загрузки и выгрузки библиотек, с которыми ваша программа не была напрямую связана. Однако технически они выходят за рамки базового языка C ++.

Джо Зи
источник
Все ? Даже те, которые явно загружаются после main?
Джеймс Канце
Что ж, C ++ технически не определяет динамически загружаемые библиотеки, поэтому в чистом C ++ мое утверждение верно. Итак, заштрихуйте его «Все, за исключением инициализаторов и объектов области видимости файлов, содержащихся в библиотеках DLL / DSO, загружаемых после достижения main». В этом случае mainон пуст, поэтому эти DLL / DSO должны быть загружены деструкторами, что чертовски неверно. Но поскольку это информатика, я полагаю, нам следует быть осторожными со словами типа «все».
Joe Z
Я добавил оговорку «все» к своему ответу выше, а также добавил примечание о dtors.
Joe Z
Да, но, надеюсь, это произойдет. Pre C ++ 11 содержал некоторую ласковую формулировку, предназначенную для разрешения DLL, но на практике это означало только то, что технически гарантия не всегда была, даже если она была во всех реальных реализациях, и от нее зависел большой код. По крайней мере, в C ++ 11 это исправлено.
Джеймс Канце