314

Столкнулся с такой задачей на собеседовании:

Написать программу, которая печатает в консоли фразу Hello world

И всё бы ничего, если бы после этого стояла точка. Но фраза продолжается:

при условии, что функция main() данной программы выглядит следующим образом:

int main() 
{
    return 0;
}
αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
hynter26
  • 3,197
  • 2
  • 18
  • 10

12 Answers12

311

Ответ очень прост. Их даже несколько. Самый простой, основанный на свойстве глобальных статических объектов.

int printHelloWorld() { printf("Hello World"); return 0; }
static int testVar = printHelloWorld();
int main ()
{
    return 0;
}

Это, если С стиль. С++ стиль: создаем класс, в конструкторе добавляем вывод, будет вывод до main, в деструкторе - после.

Nicolas Chabanovsky
  • 51,426
  • 87
  • 267
  • 507
  • 56
    можно ещё короче int i = puts("Hello World"); int main(void) { return 0; } – psyhitus Jan 20 '11 at 11:23
  • 4
    создаем класс

    Имеется в виду - нужен объект класса :-) Т.к. само объявление класса ничего не вызывает.

    – gecube Mar 30 '11 at 23:56
  • 2
    А оптимизация не порежет ли инициализацию к неиспользуемой статичекой переменной? Или это только в шарпе статик инициализируется при первом обращении в коде, до этого он мертв? – Чад Aug 11 '12 at 11:21
  • 1
    @Чад хоть и поздно, но отвечу. В шарпе статик инициализируется не при первом обращении - а при загрузке класса. В Си/С++ может быть выкинут лишний модуль, на который нет ссылок - но если модуль присутствует, то все статические переменные будут инициализированы. – Pavel Mayorov Nov 22 '16 at 11:23
  • 3
    Во-первых, не понятно, почему это названо "С стилем". В языке С динамическая инициализация невозможна. Так что сам подход в самой своей сути - именно С++ стиль, что с классами, что без. Во-вторых, без предварительного обявления функции printf программа некорректна. Это относится и к варианту с puts. – AnT stands with Russia Jul 01 '17 at 05:55
  • Увидев этот код, удивился насколько некоторый работодатель пытается заморочиться с тестированием на собеседовании, просто нет слов! – Slava Feb 25 '19 at 18:54
204

Используем препроцессор.

#define return puts("Hello World"); return

int main () { return 0; }

Не такой красивый вариант как со статикой, но тоже работает.

psyhitus
  • 3,667
  • 2
  • 20
  • 29
  • 6
    Вот у меня были подобные мысли, но смутило то, что return занятое слово, разве его дефайном можно? – hynter26 Jan 20 '11 at 07:06
  • 25
    В том-то и дело, что дефайном можно все что угодно. В этом его мощь и... опасность :) – y0prst Jan 20 '11 at 08:36
  • 54
    Старая шутка на ум пришла #define true false – psyhitus Jan 20 '11 at 14:21
  • 15
    И сразу второй строчкой
    #define false true
    :-)
    – gecube Mar 31 '11 at 10:42
  • 5
    Единственный вариант, работающий в C и C++ – avp Sep 28 '11 at 15:36
  • нет не единственный. наоборот, про решение с классом и конструктором можно сказать "единственный вариант НЕ работающий в С и С++" – sudo97 Oct 26 '11 at 14:36
  • Я когда пишу для МК. Делаю так: #define begin { #define end } – vanyamelikov May 13 '13 at 19:45
  • @vanyamelikov а почему? – 4per Nov 22 '16 at 11:50
  • 5
    @hynter26: Нет, переопределять return нельзя, по каковой причине данное решение является некорректным. "17.6.4.3.2 Macro names 2 A translation unit shall not #define or #undef names lexically identical to keywords [...]" Это требование вроде связано с использованием стандартной библиотеки. Но в данном случае она как раз используется, хоть и не правильно. – AnT stands with Russia Jul 01 '17 at 06:55
107

True programmer напишет это в hex редакторе в машинных кодах. Даже функция main не нужна.

BB 11 01 B9 0D 00 
B4 0E 8A 07 43 CD 
10 E2 F9 CD 20 48 
65 6C 6C 6F 2C 20 
57 6F 72 6C 64 21

Немного исправим, воспользовавшись примером выше для соответствию заданию.

using namespace std;
#include<ofstream>

class hello{
public:
hello()
{
    ofstream hello;
    hello.open ("hello.com");
    hello << "»..№..ґ.Љ.CН.вщН Hello, World!";
    hello.close();
    system("hello.com");
}put;

int main(){
    return 0;
}

Вуаля мы получили программу на языке С++ которая возвращает .com файл, затем запускает его, после чего этот файл через BIOS прерывание выводит на экран Hello, world! При этом наша программа имеет пустую функцию main и не обращается напрямую к стандартном выводу в командную строку. Работает только в DOS command promt.

Real KEK
  • 1,329
igumnov
  • 7,806
99

Статические переменные и дефайны не обязательны:

#include <cstdio>
int s = printf("Hello world\n");

int main()
{
    return 0;
}
αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
Alex Kapustin
  • 11,694
  • 1
  • 20
  • 22
  • 16
    Замена статической переменной на внешнюю означает "статические переменные необязательны"? – alexlz Apr 19 '12 at 15:12
  • 2
    Хоть переменная i и обладает static storage duration, т.е. с этой точки зрения является "статической", это - первый правильный ответ, благодаря не забытому <stdio.h>. Лучше было бы, конечно, <cstdio>. Небольшой недочет - отсутствие \n на конце выводимой строки. С++ не оговаривает поведение текстовых потоков, в которых последняя строка не заканчивается на \n. – AnT stands with Russia Jul 01 '17 at 06:36
74

Вот ещё способ:

#include <stdio.h>

void hello() { puts("Hello, World!"); } #pragma data_seg(".CRT$XIY") void(*pinit)()=&hello;

int main() { return 0; };

Только оказалось, что он только в дебаге работает.

ПРАВКА:

А ещё можно подменить точку входа:

#pragma comment(linker, "/ENTRY:Main") //Вместо прагмы можно использовать параметр командной строки
#include <windows.h> //CRT с его printf и puts (не говоря уже о cout) мы, к сожалению, потеряли, поэтому придётся использовать средства ОС

int main(void) { return 0; }

void Main() { WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), "Hello, World!\n", 14, nullptr, nullptr); }

Кстати, у меня эта программка занимает 656 байт после того, как я немного похимичил с ключами компиляции.

devoln
  • 5,441
  • 2
    що означає макрос pragma? – sudo97 Dec 02 '11 at 15:09
  • 4
    #pragma - это не макрос, а директива препроцессора. pragm'ы бывают разные, их поддержка зависит от компилятора. Конкретно эта записывает указатель на функцию в секцию CRT - стандартной библиотеки C, в недрах которой определена функция mainCRTStartup, которая вызывает main. Перед этим она вызывает все функции из этой секции. – devoln Dec 02 '11 at 15:16
  • 1
    @GLmonster, а я попробовал Ваш код в MinGW g++ в Win7.

    Собирается, но ничего не выводит (точка входа не изменилась). Вы в чем проверяли ?

    – avp Aug 11 '12 at 19:26
  • 1
    В Visual studio. Прагмы не кросскомпиляторные. Вместо прагмы можно ключ компилятора использовать(которые тоже не кросскомпиляторные). – devoln Aug 12 '12 at 06:59
  • 1
    Понятно, что к исходной задаче эти враианты никакого отношения не имеют, как не имеют они никакого отношения к языку С++. – AnT stands with Russia Jul 01 '17 at 06:29
73
#include<iostream>

class hello {
    public:
        hello(){ std::cout << "Hello, world!\n"; }
} put;

int main(){
    return 0;
}
Nicolas Chabanovsky
  • 51,426
  • 87
  • 267
  • 507
sudo97
  • 1,823
64
#include <iostream>

const std::ostream& out = std::cout << "Hello World" << std::endl;
Baho
  • 929
  • 7
  • 8
54

Помимо многих предложенных ранее вариантов с выводом текста до выполнения main, предложу вариант с выводом после выполнения main:

#include <cstdio>
#include <cstdlib>

void hw() {
    puts("Hello world");
}

int v = atexit(hw);

int main() {
    return 0;
}
αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
47

gcc поддерживает атрибут "constructor", который помещает функцию в init-секцию ELF-файла. Что, в свою очередь, означает, что эта функция вызовется перед функцией main. То есть, то, что нам нужно.

void __attribute__((constructor)) init {
  printf("Hello world!");
}
Ilmirus
  • 1,573
  • 10
  • 15
25
$ cat hellower.cpp 
#include "helper.cpp"
int main(void) {
        return 0;
}

второй файл:

$ cat helper.cpp 
#include <stdio.h>

int hello_helper() {
        printf("Hello World\n");
        return 0;
}

static int a = hello_helper();

Лаунч:

$ g++ hellower.cpp
$ ./a.out 
Hello World
  • И чем это отличается от верхнего ответа? – VladD Nov 15 '15 at 16:50
  • 1
    тем что инклуд всего лишь один перед пустым main() и всё. – wonderedman Dec 08 '15 at 12:08
  • 1
    @VladD: Хотя бы тем, что не забывает включить <stdio.h>. – AnT stands with Russia Jul 01 '17 at 07:21
  • 1
    @AnT: Ну разве что, но это ведь всегда считалось на SO несущественным, точно так же, как и другие называния переменных или там другой стиль скобок. Идейно — ничем. – VladD Jul 01 '17 at 07:48
  • 2
    @VladD В обычных задачах - может быть. Но в рамках специфической задачи, явно оговаривающей, в какой части кода разрешается делать исправления, а в какой - нет, расположение каждого добавленного в код символа имеет значение. – AnT stands with Russia Jul 01 '17 at 08:06
22

Первый файл (строго соответствует условию задачи):

$ cat main.cpp
int main(void)
{
    return 0;
}

Второй файл:

$ cat hello.cpp
#include <stdio.h>

int hello_helper() {
    printf("Hello World\n");
    return 0;
}

static int a = hello_helper();

Makefile для компилятора g++:

$ cat makefile
TARGET = hello
PREFIX = /usr/local/bin
.PHONY: all clean install uninstall
all: $(TARGET)
clean:
    rm -f $(TARGET) *.o
main.o: main.cpp
    g++ -c -o main.o main.cpp
hello.o: hello.cpp
    g++ -c -o hello.o hello.cpp
$(TARGET): main.o hello.o
    g++ -o $(TARGET) main.o hello.o
install:
    cp $(TARGET) $(PREFIX)/$(TARGET)
uninstall:
    rm -f $(PREFIX)/$(TARGET)
serge
  • 221
  • 1
    цели main.o и hello.o не нужны в случае использования программы gnu/make – aleksandr barakin Nov 30 '17 at 14:54
  • 1
    Это инкрементная компиляция. Представим, что наша программа состоит из десятка- другого исходных файлов. Мы вносим изменения в один из них, и хотим ее пересобрать. Использование подхода описанного Вами приведет к тому, что все без исключения исходные файлы будут снова скомпилированы, что негативно скажется на времени перекомпиляции. Решение - разделить компиляцию на два этапа: этап трансляции и этап линковки, что значительно сокращает время отладки и перекомпиляции больших проектов. – serge Nov 30 '17 at 16:41
  • 2
    то ли вы меня не поняли, то ли я вас не понимаю. в приведённом вами файле makefile не нужны четыре строки с 7 по 10-ю (если интерпретация файла выполняется программой gnu/make). пожалуйста, проверьте и убедитесь сами (программа gnu/make будет выполнять ровно те же действия как при наличии, так и при отсутствии этих четырёх строк). – aleksandr barakin Nov 30 '17 at 17:49
  • 1
13

Вот в стиле C++

#include <iostream>

class StaticOutput
{
    static int print();
    static int out;
};

int StaticOutput::out = StaticOutput::print();

int StaticOutput::print()
{
    std::cout << "Hello World!" << std::endl;
    return 0;
}


int main()
{
    return 0;
}
Range
  • 2,693