Як можна нічого не оголошувати всередині main () в C ++ і при цьому мати робочий додаток після компіляції?


86

В інтерв’ю я зіткнувся з таким запитанням:

Ваш друг подарував вам один файл вихідного коду, який друкує номери Фібоначчі на консолі. Зверніть увагу, що блок main () порожній і в ньому немає жодних операторів.

Поясніть, як це можливо (підказка: глобальний екземпляр!)

Я дуже хочу знати про це, як таке може бути навіть можливим!


26
Подивіться на підказку!
Р. Мартіньо Фернандес

14
Тому що це те, про що 1) я не чув, 2) корисна дрібниця, тому що люди просять про це в інтерв'ю, 3) цікаве застосування мови, яку потрібно знати, щоб 4) я міг її розпізнати і вдарити когось в обличчя іржавий шив, якщо я бачу, що вони насправді використовують це у виробничому коді.
OmnipotentEntity

4
Грамотний, професійний програміст на C ++ знатиме відповідь на це питання. Якщо метою цього запитання для співбесіди є визначити, чи особа, з якою беруть інтерв’ю, є компетентним, професійним програмістом на C ++, тоді це питання не повинно дати їм відповіді.
Джон Дайблінг,

1
В умовах інтерв'ю однією альтернативою може бути логіка всередині будь-якої функції коду та реєстрація виводу за допомогою assertабо #pragma messageтощо. Це призведе до перенаправлення виводу на консоль під час компіляції. Можливо, програма навіть ніколи не буде повністю скомпільована, але це, безсумнівно, цікавий спосіб показати своє "нестандартне" мислення під час співбесіди. Це задовольняє цитоване запитання, оскільки НЕ згадує нічого про генеруваний двійковий файл; скоріше йдеться лише про файл C, який може відображати "речі" на консолі. ;-)
TheCodeArtist

1
Це було інтерв’ю для МОКК ? :-) Гаразд, я визнаю, що часто роблю це для ініціалізації своїх заводів або виконання якогось тестового коду. До речі, `` один файл вихідного коду '' - це також натяк на те, що вхідний пінт (основний за замовчуванням) не замінюється лінкером.
Валентин Хайніц

Відповіді:


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гарантовано буде побудований.
James Kanze

18

Сподіваюся, це допомагає

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

int main()
{
}

Отже, як тільки оголошується глобальна змінна класу, викликається конструктор, і ви додаєте логіку для друку ряду Фібоначчі.


9

Так це можливо. Вам потрібно оголосити глобальний екземпляр об’єкта, який обчислює числа Фібоначчі в конструкторі об’єктів.


6
Вам потрібно оголосити глобальний екземпляр об’єкта, ініціалізатор якого обчислює числа Фібоначчі.
James Kanze,

4

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

Тут ви можете отримати приклад із числами Фібоначчі

Якщо ви використовуєте його в конструкторі статичного класу, і ви можете писати цифри без необхідності писати будь-який код у головній функції.

Сподіваюся, це допоможе вам.


3

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


3

Усі конструктори [*] для об'єктів файлової області викликаються перед досягненням main, як і всі вирази ініціалізатора для не-об'єктних змінних області файлу.

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

[*] Зверніть увагу, що "все" ігнорує поведінку динамічного завантаження та вивантаження бібліотек, з якими ваша програма не була безпосередньо пов'язана. Однак технічно вони перебувають поза базовою мовою C ++.


Всі ? Навіть ті в dll, які явно завантажуються після main?
James Kanze,

Ну, C ++ технічно не визначає динамічно завантажувані бібліотеки, тому в чистому C ++ моє твердження правильне. Отже, затініть його "Все, збережіть для ініціалізаторів та об'єктів обсягу файлів, що містяться в DLL / DSO, завантажених після досягнення основного". У цьому випадку mainпорожній, тому ці DLL / DSO повинні були завантажуватися деструкторами, що є надзвичайно збоченим. Але, маючи на увазі інформатику, я вважаю, що нам слід бути обережними зі словами типу "всі".
Joe Z

Я додав застереження щодо "всього" до моєї відповіді вище, а також додав примітку про dtors.
Joe Z

Так, але, сподіваємось, це прийде. Pre C ++ 11 містив деякі формулювання ласки, призначені для дозволу бібліотек DLL, але що на практиці означало лише те, що технічно гарантія не завжди існувала, хоча вона була у всіх реальних реалізаціях, і багато коду залежало від цього. C ++ 11 це виправлено, принаймні.
James Kanze
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.