Вы помните свое первое собеседование? Я свое помню отлично, преподаватель дольше обычного гонял мою плис в симуляторе Keil-C, придирался к любой мелочи, докапывался до каждой запятой в коде прошивки. А потом начал гонять по алгоритмам трассировки печатных плат, которые мы должны были проходить только в следующем семестре. Я уже мысленно готовился на допсу, видно же что валит, как и предыдущих двух одногруппников. Но в конце сдачи лабы по проектированию мк преподаватель спросил хочу ли я делать "железное железо для железной дороги?" (с). Студенту второго курса ИТМО, которого кормили родители, и подрабатывавшего разгрузкой вагонов ночами, это было сродни офферу в гугль. С тех пор я много раз побывал с обеих сторон стола, и в качестве испытуемого, и как придирчивый лид (отнюдь этим не горжусь, но и не стыжусь), и как группа поддержки у коллег из соседних отделов. Всегда хотелось надеяться, что наши собеседования - это не таинственные квесты, где каждая задача - это каст сложного заклятия, а ошибки не выкидывают с данжа.
Но сначала сказка о том, как к нам попал Миша: однажды в славном городе Панкт-Сетербург погромист-джедай по имени Михаил отправился на собеседование в компанию "Кодозавры". Он был уверен, что знает все, и даже изучил новейшие фреймворки "NoScript" и "Unreal С--". Как он думал, ничто не может его остановить. Когда Миша пришел на собеседование, его встретил HR-менеджер по имени Анна. Она с улыбкой спросила: "Расскажите, как вы бы пояснили своей бабушке, что такое мьютекс?". Ага, прям с порога наш доблестный HR накинул Мишке затравочный вопрос. Ну а чё, это ж сказка, герои на белых самокатах и все такое...

Так что пусть наша Аня шарит в примитивах синхронизации. Мишка ушами не хлопал, улыбнулся и начал рассказывать про асинхронные задачи, колбэки и атомики. Любая бабушка будет гордиться таким внуком, подумал он. Затем Михаила пригласили в избу, Анечка упорхнула на кухню варить кофф, не в силах осознать полученную информацию. Ой, нет - она пошла дописывать тестовое задание для новичков. Впрочем неважно, это же сказка - нашего героя провели по офису, показали крепостных на полях румяных девопсов (блин, некрасиво получилось) и направили в переговорку, куда чуть позже подошел лид отдела, в который и хотел попасть тогда еще юный наш герой. Не давая никому в комнате расслабиться, он сразу выдал стандартную загадку: "Как вы перевернете строку задом наперед без использования дополнительной памяти?", и уселся напротив кандидата писать код в своем смак-буке, кинув на себя спелл невидимости, чтобы не мешать Мише думать. Заклинание невидимости не прошло проверку на интеллект, но богатырь не подал виду и попробовал решить задачу в уме, ведь листочка тоже не дали. В итоге в голове у него произошел целый карнавал символов и индексов, в конце концов, он пришел к решению и объявил его "Въ развращеніи словесъ, начнити отъ крещениа и двигатисѧ ко началу, мѣнити буквы, въ обратномъ порядкѣ". Сказанного хватило, чтобы в переговорку телепортнулся босс данжа, т.е. тех лид студии и начал кидать в Мишу файрболы, ой... задавать скользкие и каверзные вопросы. Мишаня отбивался как мог, не путался в ответах, в минуты передышек провел пару атак световыми мечами и давил противника вопросами о техстеке, билдферме и процессе ревью. Наконец мана у босса в кружке опустилась до критического уровня, на последнем глотке тех лид студии "Кодозавры" дал задание переписать пульт управления ракетой на NoScript. Для камлания оставили смак-бук, час времени и какую-то матерь, призыв духов Гугола и Яру особых успехов не принес, зато дважды являлась Аннафея и предлагала испить живой воды, вода правда была коричневого цвета, и отчего-то пахла кофем, который не Нескафе, но Мишаня в фей не верил и изгонял искусительницу вопросами про спинлоки, после чего Аннафея таяла легкой дымкой.
Звезды сошлись, код смешался с загадками, магия ctrl+shift+B позволила увидеть три заветных слова: "Скомпилилось без ошибок!". Из данжа Михаил выходил победителем, апнул уровень харизмы и поднял скилл уверенности в своих силах. Босс как обычно в досадной злобе буркнул свое финальное проклятие - "Мы перезвоним вам через неделю".

И не перезвонил. И отлично, потому что, тогда бы Мишаня не получил оффер от Gaijin, где и начал свое приключение в мире 3d-движков, которые выжимают 60фпс на дохлом железе при сотнях активных объектов на сцене и пром плюсов в полный рост. Знакомо?
Уфф... про световые мечи наверное было лишнее. Сказочным путем Мишани мне довелось пройти всего один раз, видимо сказывались знакомства. Через некоторое время я сам стал проводить собеседования, подбирая людей в команду. Тайны из наших собственных вопросов я никогда не делал, они както сами копились в текстовом файле и даже какое-то время висели на сайте компании в разделе "Карьера" и любой мог их посмотреть. Да и вопросами то их назвать сложно, скорее разные интересные фрагменты кода, которые ведут себя не так как предполагалось, какие-то сложнее, какие-то проще.
Поначалу, участвуя в других собеседованиях, я копировал и сами вопросы и устоявшиеся модели поведения. Но потом это стало надоедать, смысла в том что бы сильно налегать на матчасть или алгоритмы, если человек не впишется в команду не сильно много, если через пару месяцев придется искать нового. А ведь это и мои пара месяцев, которые я потрачу на обучение, отниму это время от своих задач и команды. Я не говорю, что не надо спрашивать алгоритмы, наоборот надо. Пусть человек сам расскажет какие знает и детально расскажет про пару по своему выбору. Перепробовав разные форматы собеседований, от сказочных, хорошо что был мудрый тех лид, который после первого такого праздника вызвал к себе и сказал, чтобы такой фигни он больше не видел, так что этот шаг мы проскочили сразу. Почти. Макс сорян, тебе просто не повезло, я был молод и глуп. Потом были стандартные на 10-15 вопросов на листе в стиле блиц опроса с минутой на обдумывание и небольшого тестового на час здесь и сейчас.
Позже мы с командой пришли к довольно необычному процессу отбора, назовем его "собес наоборот" - именно после таких собесов мы получили несколько отличных спецов в отдел, которые бы не попали к нам по другому. Как потом оказалось, один ненавидел тестовые и просто уходил с собеса, когда ему предлагали их делать, при этом у него шикарный гитхаб (https://github.com/megai2/d912pxy) и человек в одиночку тянул нехилый пет-проект на 1к звездочек. Второй просто выгорел на прошлом месте работы, разочаровался в программировании и спотыкался на базовых вопросах по стандарту и стл, но раскрылся когда речь зашла о низкоуровневых оптимизациях на уровне асма, или надо было пореверсить алгоритм из асма и все подобное этому. Основной упор старались делать на тех людей, знаний которых не хватало в команде, даже если он не проходил по другим критериям. Не утверждаю, что это панацея, собес это в любом случае разговор двух сторон, которые пытаются продать себя друг другу. Как представитель компании, я продаю компанию человеку и смысла врать про пульт управления ракетой нет, если по выходу на пульте придется только перекрашивать кнопки и обновлять разметку. Хвала небесам, это не про игрострой, интересных задач тут навалом, успевай разгребать только.

Со стороны человека который идет в компанию, (как мне кажется) тоже особо нет смысла преувеличивать свое умение чинить пульты разных видов, с плюсиками или бемолями, квадратные или змеевидные. Или хвастаться починкой пультов от Когдазавра и Роботоноида версии 2.0. Все равно квадратный зеленый пульт мы чинить не умеем, или умеем, но еще не знаем как и вечером придется читать документацию. Как представитель себя любимого, я стараюсь получить побольше денег и (поменьше звиздеца в любых проявлениях) интересные для меня качество и количество задач.
В чем идея "собеса наоборот", все тот же список вопросов, будь он неладен, только мы просили выбрать их по желанию и задать нам, сами отвечали на вопросы, иногда сознательно опуская некоторые моменты, иногда внося неточности, а наш будущий коллега, подмечал или не подмечал это. Добавлял и поправлял, или не поправлял наши рассказы, в большинстве случаев человека вовлекали в разговор.
На тех собесах я не помню чтобы мы писали сложный код, чаще вообще не писали, разве что были небольшие пояснения на бумаге. Ломалась сама атмосфера собеседования как экзамена, и вот уже кандидат пытается подловить нас на ответах, задает вопросы не из списка, несколько раз не могли потом успокоить по полчаса - вопросы сыпались один за другим, приходилось кастовать смак-бук и тестовое. Обращали внимание на умение человека ясно и внятно изложить свои мысли, проговорить идею, задать вопрос, потому что матчасть можно подтянуть, не сейчас так через месяц-другой. Скилл в починке скругленных серебристых тачскринов тоже со временем наработается, если человек не особо ленив. А вот культуру умения вести разговор, обмена идеями и обсуждения технических вопросов в ревью, не обидив при этом коллег и не вернув его 120 раз на доработку, осилить (а главное принять) намного сложнее.

Собственно это все, что я хотел рассказать. А теперь танцы, т.е. вопросы. Часть вопросов уже поднимались вот тут (https://habr.com/ru/articles/764514/ дичь от Феди, я смотрю многим пришлась по вкусу), надеюсь эти будут не хуже, могут быть дубли. Какие-то вопросы покажутся вам простыми, значит вы много пишите кода, и вообще человек хороший:
0х1...
Начну пожалуй с классического вопроса, что будет выведено в результате работы программы?
void sayHello() {
std::cout << "Hello, World!\n";
}
void sayНello() {
std::cout << "Goodbye, World!\n";
}
int main() {
sayНello();
return 0;
}
А что не так?
А надо скомпилить, тогда в консоль будет выведено "Goodbye, World!". Потому что вторая sayHello написана с использование юникода, который к сожалению не всегда палится. И она же вызывается (clang, latest)
sayHello(): # @sayHello()
push rbp
mov rbp, rsp
mov rdi, qword ptr [rip + std::cout@GOTPCREL]
lea rsi, [rip + .L.str]
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@PLT
pop rbp
ret
"_Z9sayНellov": # @"_Z9say\D0\9Dellov"
push rbp
mov rbp, rsp
mov rdi, qword ptr [rip + std::cout@GOTPCREL]
lea rsi, [rip + .L.str.1]
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@PLT
pop rbp
ret
0х2...
А эта функция обладает удивительной способностью изменять реальность в зависимости от того, сколько ног у ее вызывающего.
int abs_legs(int my_legs) {
if (my_legs < 0) {
return -my_legs;
}
}
А что не так?
Функция, возвращающая значение, должна возвращать значение (хм!) из всех возможных веток, потому что всегда найдется такое условие которое приведет к неопределенному поведению. Кажется это правило работает не только для функций, которые возвращают значение.
int abs_legs(int my_legs) {
if (my_legs < 0) {
return -my_legs;
}
return my_legs;
}
0х3...
Эта функция не раскрывается полностью, оставляя свою магию за покровом компилятора. Где деньги, Зин?
int get_money(int index, const int *pockets) {
int a = index + pockets[++index];
// ...
return a;
}
А что не так?
Компилятор может переставить порядок операции для index + pockets[++index], и это приведет к неоднозначному поведению с разными настройками оптимизации. Неупорядоченная или неопределенная последовательность операций приведет к сайд эффекту при работе с переменной index
int get_money(int index, const int *pockets) {
++index;
int a = index + pockets[index];
// ...
}
0х4...
Что будет выведено в bufMatrix?
void umcp_read_buffer_from_pipe() {
char bufKernel[12];
char bufMatrix[12];
std::cin.width(12);
std::cin >> bufKernel;
std::cin >> bufMatrix;
}
А что не так?
В этом примере первое чтение не приведет к переполнению, и заполнит bufKernel усеченной строкой. Но второе чтение может переполнить bufMatrix, чтобы этого не произошло надо также вызвать std::cin.width(12); перед получением bufMatrix. Или воспользоваться безопасной работой через строки.
void umcp_read_buffer_from_pipe() {
std::string bufKernel, bufMatrix;
std::cin >> bufKernel >> bufMatrix;
}
0х5...
Кажется, в этом коде нильзя-программер просто смешивает случайности с абсурдом и называет это "настройкой отображения".
std::string str_func();
void display_string(const char *);
void set_display_options() {
const char *str = str_func().c_str();
display_string(str);
}
А что не так?
Здесь std::string::c_str() вызывается для временного объекта std::string. Полученный указатель будет указывать на освобожденную, но возможно еще валидную, память после уничтожения объекта std::string в конце выражения присваивания. Правда не очень на это надейтесь, это приведет к неопределенному поведению при доступе к этому указателу.
std::string str_func();
void display_string(const char *s);
void set_display_options() {
std::string str = str_func();
const char *cstr = str.c_str();
display_string(cstr);
}
0х6...
Сколько раз за вечер бар будет закрыт?
void evening() {
int *bar = new int;
std::shared_ptr<int> p1(bar);
std::shared_ptr<int> p2(bar);
}
А что не так?
Закроют бар один раз, потом попробуют снести город, в котором он стоит. Здесь два несвязанных смарт указателя созданы на основе одного и того же базового значения. Когда время жизни переменной p2 закончится, она удалит ресурс, которым управляет. Об этом не узнает переменная p1, которая попробует выполнить удаление этого ресурса повторно, что-то да удалит в итоге. Но это уже совсем другая история.
void evening() {
std::shared_ptr<int> p1 = std::make_shared<int>();
std::shared_ptr<int> p2(p1);
}
0х7...
Почему утром опасно ходить в bar?
void morning(const std::string &owner) {
std::fstream bar(owner);
if (!bar.is_open()) {
// Handle error
return;
}
bar << "customer";
std::string str;
bar >> str;
}
А что не так?
Потому что можно не выйти. Данные добавляются в конец файла, а затем считываются из того же файла. Однако, поскольку поскольку указатель в файле остался на прежнем месте в конце, то попытка прочитать данные из конца файла приведет к UB. В бар надо ходить вечером, тогда все "замечательно выходит" (с).
void evening(const std::string &owner) {
std::fstream bar(owner);
if (!bar.is_open()) {
// Handle error
return;
}
bar << "customer";
std::string str;
bar.seekg(0, std::ios::beg);
bar >> str;
}
0х8...
Какое одно слово надо изменить, чтобы этот код заработал?
struct Foo {
void *foo;
struct foo *next;
};
static Foo foos;
static std::mutex m;
void consume_list_element(const std::condition_variable &condition) {
std::unique_lock<std::mutex> lk(m);
if (foos.next == nullptr) {
condition.wait(lk);
}
// Proceed when condition holds.
}
А что не так?
После того как будет получен сигнал для condition_variable condition, нужно повторно проверить не забрал ли кто элемент пока мы переключали контекст или поток находился в ожидании. Если этого не сделать, возможна работа с указателем на null
while (foos.next == nullptr) {
condition.wait(lk);
}
0х9...
Почему этот rand() работает как генератор случайных чисел от "Sony"? Здесь надо немного пояснить про этот мем, для генерации rand() в своих секретных ключах Сони использовала секретную функцию, которая из-за ошибки реализации возвращала одно и тоже число.
int main() {
std::string id("ID");
id += std::to_string(std::rand() % 10000);
// ...
}
А что не так?
Полученное путем вызова функции rand() число предсказуемо и имеет ограниченную случайность. Чтобы избавиться от повторения сгенерированной последовательности в некоторых реализациях std::rand(), надо выбрать генератор, предоставляющий истинно неповторяющуюся последовательность. Например mtXXXX (https://dl.acm.org/doi/10.1145/272991.272995)
int main() {
std::string id("ID");
std::uniform_int_distribution<int> distribution(0, 10000);
std::random_device rd;
std::mt19937 engine(rd());
id += std::to_string(distribution(engine));
// ...
}
0хА...
Получит ли ведьмак монеты?
class Witcher {
int i;
public:
virtual int TakeCoins(int coins) { return i += coins; }
};
int main() {
Witcher *geralt = new Witcher();
memset(geralt, 0, sizeof(Witcher));
std::cout << geralt->TakeCoins(100);
return 0;
}
А что не так?
Лучше не использовать memset() для работы с полиморфными типами, она потрет указатель на vtbl в классе, такой объект становится невалидным и попытка поработать с ним будут приводить к UB
class Witcher {
int i = 0;
public:
virtual int TakeCoins(int coins) { return i += coins; }
};
int main() {
Witcher *geralt = new Witcher();
std::cout << geralt->TakeCoins(100);
return 0;
}
0xB...
Станет ли бард пьяницей?
class Bard {
int _beer;
int _meal;
public:
Bard(int meal) : _meal(meal), _beer(_meal - 1) {}
};
А что не так?
Cписок инициализации мемберов нарушен и сначала будет установлена переменная _beer, а затем будет инициализирована переменная _meal. Поскольку порядок объявления переменных не соответствует тому, что заявлено в описании класса, попытка прочитать значение _meal приводит к тому, что оно имеем неопределенное значение. Скорее всего бард сопьется без закуси.
class Bard {
int _beer;
int _meal;
public:
Bard(int beer) : _beer(beer), _meal(_beer + 1) {}
};
0хС...
В каком случае выстрел по ногам будет особенно результативным?
for (auto format = begin(formats), __end = end(formats); format != __end; ++format) {
if (snd::CodecNamesEq(....)) {
format.is_stereo = true;
formats.push_back(stereo_format);
}
}
А что не так?
Если в процессе push_back() произойдет реалокация formats, итератор format будет указывать уже на удаленную память, работа с которой вызовет кучу проблем.
0хD...
Этот код лучше попробовать собрать самому, чтобы понять админ ли вы? (https://onlinegdb.com/rMnPPq5et)
int main() {
std::string access_level = "user";
if (access_level.compare("user // Check if admin ")) {
std::cout << "You are an admin.\n";
}
return 0;
}
0xE...
Иногда в с++ просто вхух и работает!
.∧_∧
( ・ω・。)つ━☆・*。
⊂ ノ ・゜+.
しーJ °。+ *´¨)
.· ´¸.·*´¨) ¸.·*¨)
(¸.·´ (¸.·'* ☆ Wizz, just works! ☆

0xF...
Ни один Мишаня в результате наших собесов не пострадал, Костя ответил на все вопросы и добавил в список пару новых. Особо отличившиеся получили предложение присоединиться к команде, просто умные люди шли дальше на собес в iOS/Android team. А Максим потом нашел себя в бекэнд команде красного банка.
Вот и сказочке конец, а кто дочитал, благодарю за внимание.