Главная » Хабрахабр » [Из песочницы] Как не надо писать код

[Из песочницы] Как не надо писать код

image

Хотите узнать как непредсказуемо могут повести себя несколько простых строк кода? Готовы погрузиться с головой в дивный мир программирования?

Если ваш ответ "Да!" — добро пожаловать под кат.

Вас будут ждать несколько занимательных задачек на С или С++.

Правильный ответ с объяснением всегда будет спрятан под спойлером.

Удачи!

Про самую короткую программу

main;

Что будет если скомпилировать эту программу компилятором языка C?

  1. Не cкомпилируется.
  2. Не слинкуется.
  3. Скомпилируется и слинкуется.

Ответ:

В C можно опустить тип возвращаемого значения у функций и при объявлении переменных, по-умолчанию он будет int-ом. Это валидный код на языке C.
Почему? В этом случае линковщик думает что под именем main находится функция. А ещё в С нет различия между функциями и глобальными переменными при линковке.

Про fork

#include <iostream>
#include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork();
}

Сколько раз будет напечатано "Hello world!"?

  1. 1000
  2. меньше
  3. больше

Ответ:

IO операции буферизуется для улучшения производительности.
Вызов fork() породит новый процесс, с copy-on-write дубликатом адресного пространства.
Буферизованные строчки будут напечатаны в каждом процессе.

Про индексы

#include <iostream> int main() ; std::cout << (4, (1, 2)[array]) << std::endl;
}

Что напечатет этот код?

  1. 1
  2. 2
  3. 3
  4. 4
  5. Ошибка компиляции
  6. Не определено стандартом.

Ответ:

Он отбрасывает свой левый аргумент и возвращает значение правого. Порграмма напечатает 3.
Почему так?
Сначала посмотрим на индекс: array[index] == *(array + index) == *(index + array) == index[array]
Дальше мы имеем дело с бинарным оператором запятая.

Про регулярное выражение

#include <regex>
#include <iostream> int main() { std::regex re { "(.*|.*)*O" }; std::string str { "0123456789" }; std::cout << std::regex_match(str, re); return 0;
}

За какое минимальное время точно заматчится эта регулярка?

  1. ~ 1 мс.
  2. ~ 100 мс.
  3. ~ 1 cек.
  4. ~ 1 мин.
  5. ~ 1 час.
  6. ~ 1 год.
  7. больше времени жизни вселенной.

Ответ:

Вот и не угадали. Ха-ха! Минута! Зависит от компилятора.
На моём ноутбуке clang показывает результат примерно 100 мс.
GCC 57 секунд! Такое регулярное выражение не поддерживает backtracking.
Второй — что-то вроде жадного перебора с поиском в глубину. Серьёзно?!

Почему так?
Есть 2 подхода для реализации регулярных выражений.
Один — превратить регулярное выражение в конечный автомат за O(n**2), для регулярного выражения длиной n символов.
Сложность сопоставления co строкой из m символов — O(m). Хорошо хоть, что за минуту управились. Поддерживает backtracking.
А ещё, сложность операций с регулярными выражениями в STL никак не определена.

Про move и лямбду

#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
}; int main() { Foo f; auto a = [f = std::move(f)]() { return std::move(f); }; Foo f2(a()); return 0;
}

Какую строчку программа напечатает последней?

  1. Foo()
  2. Foo(Foo&&)
  3. Foo(const Foo&)

Ответ:

По умолчанию лямбды иммутабельны. Foo(const Foo&). При одних и тех же аргументах возвращать одни и те же значения.
Что же происходит в этом случае? Ко всем значениям указанным в [] неявно добавляется const.
Это позволяет лямбдам вести себя как обычным функциям. Можно починить объявив mutable лямбду: Когда мы пытаемся сделать move f из функции, у нас получается const Foo&&.
Это очень странная штука, компилятор не умеет с ней работать и копирует Foo.

auto a = [f = std::move(f)]() mutable { return std::move(f); };

Или сделать конструктор от Foo(const Foo&&).

Про x и bar

#include <iostream> int x = 0;
int bar(int(x)); int main() { std::cout << bar;
}

Что произойдёт если попытаться скомпилирвать и запустить это?

  1. напечатает 0
  2. напечатает 1
  3. напечатает 0x0
  4. не скомпилируется
  5. не слинкуется

Ответ:

true.
Функция bar() не используется. Программа напечатает 1.
Почему так?
int bar(int(x)); — это объявление функции, оно эквивалентно int bar(int x);.
Если вы хотите приведение типа, надо писать вот так int bar((int(x)));.
Затем мы пытаемся вывести адрес функции, он будет неявно приведён к bool, адрес функции не может быть нулём, т.е. Поэтому при линковке не будет unreferenced symbol.

Про inline

#include <iostream> inline size_t factorial(size_t n) { if (n == 0) return 1; return n * factorial(n - 1);
} int main() { std::cout << factorial(5) << std::endl;
}

Что произойдёт если ее запустить? Программа компилируется и линкуется без ошибок вот так g++ -c main.cpp -o main.o && g++ foo.cpp -o foo.o && g++ foo.o main.o -o test.

  1. Напечатается 120.
  2. Может произойти что угодно.

Ответ:

Это же С++.
Весь подвох в слове inline. Может произойти что угодно. Это лишь указание компилятору.
Он может просто вкомпилировать эту функцию в объектный файл (скорее всего, он так и сделает для рекурсивных функций).
Линковщик умеет выкидывать дубликаты не встроенных в код inline-функций.
В итоговый файл обычно попадает тот вариант, который встретился в первом объектном файле.
Программа выведет 0 если в foo.cpp:

#include <cstddef> inline size_t factorial(size_t n) { if (n == 0) return 0; return 2 * n * factorial(n - 1);
} int foo(size_t n) { return factorial(n);
}

Про конструкторы

#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } Foo(int) { std::cout << "Foo(int)\n"; } Foo(int, int) { std::cout << "Foo(int, int)\n"; } Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; } Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; }
}; void f(Foo) {} struct Bar { int i, j; Bar() { f(Foo(i, j)); f(Foo(i)); Foo(i, j); Foo(i); Foo(i, j); }
}; int main() { Bar(); }

Какая строчка будет напечатана последенй?

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. Foo(int)

Ответ:

Последней строчкой будет Foo(const Foo&, int).
Foo(i) — объявелние переменной, оно эквивалентно Foo i, а значит поле класса i пропадёт из области видимости.

Заключение

Надеюсь, вы никогда не увидите это в реальном коде.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Разбор Memory Forensics с OtterCTF и знакомство с фреймворком Volatility

Привет, Хабр! Именно ее я хочу разобрать в этом посте, всем кому интересно — добро пожаловать под кат. Недавно закончился OtterCTF (для интересующихся — ссылка на ctftime), который в этом году меня, как человека, достаточно плотно связанного с железом откровенно ...

Манекен на турбореактивно-электрическом коптере-гибриде

Очередное свидетельство, что 2019 год будет годом хайпа турбореактивных штуковин. Американский стартап ElectraFly и вояки в 2019 на ракетном полигоне в Юте планируют испытания индивидуального квадрокоптера с турбореактивным двигателем. При наборе высоты турбореактивный движок будет помогать винтам, а потом давать ...