C++: Хроники Дурки🚑 – Telegram
C++: Хроники Дурки🚑
185 subscribers
1 photo
8 links
Очень люблю C++, но это скорее уже стокгольмский синдром.
Постоянно нахожу способы стрельнуть себе в ногу.
Download Telegram
Please open Telegram to view this post
VIEW IN TELEGRAM
Первым постом в этом канале станет тот, с которого зародилось название канала.

Долгие разборы "военного синуса" привели меня к квинтессенции проблем, после которых я сумел сконструировать несколько абсолютно отвратительных примеров выстрелов себе в ногу.



#include <iostream>

#include <cmath>
double sin(std::string x) { return 1.5; }

int main() {
std::cout << "string: " << std::sin(std::string{"0.5"}) << std::endl;
}



Вот такой код, очевидно, не скомпилируется.


<source>:7:40: error: no matching function for call to 'sin(std::string)'


Что логично. Мы, конечно, объявили синус от строки, но в глобальном пространстве имен. А вызываем std::sin из пространства имен std, и там такого синуса, очевидно, нет.

Но давайте поменяем местами объявление синуса и заголовок cmath. (можем считать, что у нас есть какой-то локальный заголовок, куда такое объявление просочилось)


#include <iostream>

double sin(std::string x) { return 1.5; }
#include <cmath>

int main() {
std::cout << "string: " << std::sin(std::string{"0.5"}) << std::endl;
}



Угадайте что? Правильно: скомпилируется!


Program returned: 0
string: 1.5


А все почему? Правильно, потому что в заголовке cmath (во многих реализациях на linux) есть вот такая строчка:


using ::sin;


Будьте осторожны. Она там такая не одна.
😱5🤯3🔥2
Возвращаемся к нашему военному синусу.

Любимый пример, связанный с ним, после которого хочется переехать в комнату с мягкими стенами.


#include <iostream>
#include <cmath>

double
__attribute((weak))
sin(double x) { return 1.5; }

int main() {

std::cout << "double: " << std::sin(double(0.5)) << std::endl;
std::cout << "integer: " << std::sin(int(0)) << std::endl;
}


Эта программа нормально скомпилируется, на всех компиляторах, включая icc.
И выводит ожидаемые


Program returned: 0
double: 0.479426
integer: 0


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

Ну да ладно, давайте ничего не заменим, только закомментируем последнюю строчку.


#include <iostream>
#include <cmath>

double
__attribute((weak))
sin(double x) { return 1.5; }

int main() {

std::cout << "double: " << std::sin(double(0.5)) << std::endl;
// std::cout << "integer: " << std::sin(int(0)) << std::endl;
}


Что поменяется? Внезапно, поменяется поведение функции в предыдущей строки.


Program returned: 0
double: 1.5


Как так? Почему эта строчка повлияет на поведение предыдущей? А вот, особенности порядка применения оптимизаций icc.

Добро пожаловать к нам в дурку.
😁8🔥5🤬2
Микропостик.

Угадайте, что делает вот такой код?

union S {
long long int number;
bool yes : sizeof(long long int);
};


Вот тут можно посмотреть ответ.


Этот код проверяет, является ли число нечетным.
#include <iostream>

union is_odd {
long long int number;
bool yes : sizeof(long long int);
};

int main() {
std::boolalpha(std::cout);
for (long long int i = -10; i < 10; ++i) {
std::cout << i << " is odd? "
<< is_odd{ .number = i }.yes
<< "\n";
}
}



Вывод

-10 is odd? false
-9 is odd? true
-8 is odd? false
-7 is odd? true
-6 is odd? false
-5 is odd? true
-4 is odd? false
-3 is odd? true
...



Только способом из дурки.
😁82
Дисклеймер: я не претендую на невероятною новизну фактов. Я просто показываю всем известные примеры, на которые натыкаюсь раз за разом, раз за разом....

Вот еще один прекрасный пример такой гадости.


#include<iostream>

int main(){
int a, b;
int* p = &a;
int* q = &b + 1;
std::cout << std::hex
<< "p: " << p << "\n"
<< "q: " << q << "\n"
<< std::endl;
}


Мы сделаем указатели на две переменные. Если ставить оптимизацию до O1 включительно (после O2 начинаются вопросы на gcc) то переменные будут объявлены "по порядку", и это будут одинаковые указатели. Просто сравниваем посимвольно.


clang:
Program returned: 0
p: 0x7ffdfaf2696c
q: 0x7ffdfaf2696c

gcc:
Program returned: 0
p: 0x7ffca3076f0c
q: 0x7ffca3076f0c


Внимание, вопрос! Что будет, если мы сравним два одинаковых указателя?


#include<iostream>

int main(){
int a, b;
int* p = &a;
int* q = &b + 1;
std::cout << std::hex
<< "p: " << p << "\n"
<< "q: " << q << "\n"
<< std::endl;
std::cout << (p == q ? "equal" : "not equal")
<< std::endl;
}


И получаем внезапное:


clang:
Program returned: 0
p: 0x7ffd6bd0d16c
q: 0x7ffd6bd0d16c

equal

gcc:
Program returned: 0
p: 0x7ffe3c48696c
q: 0x7ffe3c48696c

not equal


И на самом деле - никаких противоречий со стандартом. Это просто Unspecified (даже не Undefined) Behavior.

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

Вопрос другой: вот когда на собеседовании в очередной раз задают вопросы типа "разверни список", или "проверь бинарное дерево на наличие циклов".
Вот что будет, если выдать в ответ собеседующему этот пример, и попросить доказать, что сравнение указателей в случае такого дерева/списка будет вообще работать? Без него же такую задачу не решить?

И посмотреть, как хорошо справится с ответом на этот вопрос тот, кто в очередной раз предложит развернуть список.

Хотя, разумеется, можно написать как-то так


int a, b;
auto p = reinterpret_cast<uintptr_t>(&a);
auto q = reinterpret_cast<uintptr_t>(&b + 1);
std::cout << std::hex << p << "\n" //
<< q << "\n" //
<< std::dec << (p == q) << std::endl; //
6🔥4🤯4👍1
Микропост.

Что выведет этот код?

#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(sizeof(int));
int *q = (int*)realloc(p, sizeof(int));
if (p == q) {
*p = 1;
*q = 2;
printf("%d %d\n", *p, *q);
}
}


Ответ вот тут.


А на самом деле надо бросать монетку, потому что gcc и msvc выведут

Program returned: 0
Program stdout
2 2


А clang и icc выведут
Program returned: 0
Program stdout
1 2


Но вообще, это просто типичный UB. p после реаллока становится невалидным указателем. Из хорошего - gcc это ловит со включенным
Wall.
😱2💯1
Итак, один из любимых примеров дурки.

Мы заводим структуру данных, которая конструируется от аргумента типа double. И вторую структуру, которая конструируется от первой.

А дальше создадим переменную второго типа.

#include<iostream>

struct A {
double v;
explicit A(double d) : v(d) {}
};

struct B {
double v;
explicit B(A a) : v(a.v) {}
};

int main(){
double d = 3.14;
B b(A(d));
}


Проигнорируем ворнинги компиляторов (кого оно волнует, компилируется же).

А что будет, если мы выведем значение перменной b?

int main(){
double d = 3.14;
B b(A(d));

std::cout << b << std::endl;
}


Внезапно, выведет 1:


Program returned: 0
1


Чта?!

Ну, пришло время почитать ворнинги. И выясняем, что вот это:
    B b(A(d));


не переменная. Это - функция. Дело в том, что вот два таких объявления функций, в целом, одинаковые:

void f(int a);
void q(int(a));


Мы вполне легально можем аргументы функции заключать в скобки при объявлении функций... А все, что может быть трактовано в С++ как объявление функции, должно быть трактовано как объявление функции.

И.... Я не знаю, как оно так в итоге так получается, но стреляет время от времени...

Добро пожаловать к нам в дурку.
🤯94
У мну вопрос.

Есть вот такая строчка.

// Waits for 2..6 seconds.
void SymbianEngine::updateConfigurationsAfterRandomTime()
{
int iTimeToWait = qMax(1000, (qAbs(qrand()) % 68) * 100);
#ifdef QT_BEARERMGMT_SYMBIAN_DEBUG
qDebug("QNCM waiting random time: %d ms", iTimeToWait);
#endif
QTimer::singleShot(iTimeToWait, this, SLOT(delayedConfigurationUpdate()));
}


И вот в чем вопрос... В комментарии написано, что мы ждем от 2 до 6 секунд.

В функции мы берем минимум 1000 ms (одна секунда).

Мы берем по модулю 68 (не 60) и получаем 6.8 секунды.


Почему 68? Нахрена вообще понадобилась такая функция?



Столько вопросов... Но в этот раз не к языку. 🤡
🤡5🤷‍♂1😭1
сорри, у меня сегодня отложки пытаются выскочить сразу 🙂
👀5
Приколько поговорить про гадость, которую можно отловить на этапе компиляции.

Но есть разная дурка, которая аффектит рантайм, но проходит мимо всех ворнингов (иногда это ловят всякие clang-tidy и прочие PVS студии).

Вот канонический пример:


#include<iostream>
#include <map>
#include <vector>
#include <string>

using namespace std;
using Vec = vector<string>;

void f(pair<string, Vec>&& arg) {
for (const auto& v: arg.second) {
puts(v.c_str());
}
}

int main(){
auto m = map<string, Vec>{
{"first", {"1","2"}},
{"second", {"3","4","5"}},
};

for (auto&& p: m) {
f(std::move(p));
}
}


Мы создаем мапу (она будет создана в компайл тайме), а потом все объекты из нее эффективно муваются.

Но если мы внимательно посмотрим в исхродный код, то найдем там вот такое:

        call    memcpy@PLT


Я прошу прощения...? Какое копирование?! Где?

И вообще весь блок выглядит подозрительно похоже на конструктор копирования:

.LBB1_84:
mov qword ptr [rsp + 56], r13
mov r14, qword ptr [rbx + 32]
mov rbp, qword ptr [rbx + 40]
cmp rbp, 16
jb .LBB1_87
lea r15, [rbp + 1]
mov rdi, r15
call operator new(unsigned long)@PLT
mov qword ptr [rsp + 56], rax
mov qword ptr [rsp + 72], rbp
jmp .LBB1_89
.LBB1_87:
test rbp, rbp
je .LBB1_106
lea r15, [rbp + 1]
mov rax, r13
.LBB1_89:
mov rdi, rax
mov rsi, r14
mov rdx, r15
call memcpy@PLT
.LBB1_90:
mov qword ptr [rsp + 64], rbp
mov r14, qword ptr [rbx + 64]
mov qword ptr [rsp + 88], r14
movups xmm0, xmmword ptr [rbx + 72]
mov r15, qword ptr [rbx + 72]
movups xmmword ptr [rsp + 96], xmm0
xorps xmm0, xmm0
movups xmmword ptr [rbx + 64], xmm0
mov qword ptr [rbx + 80], 0
cmp r14, r15
je .LBB1_98


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

Проблема вот в этой функции:

void f(pair<string, Vec>&& arg) 


Если поменять объявление этой функции на

void f(pair<const string, Vec>&& arg) {


то этот блок строк на 70 машинного кода уйдет, а перфоманс выровняется.

Почему так? Если посмотреть на cpp reference, то мы увидим, что

key_type Key
mapped_type T
value_type std::pair<const Key, T>


значение в мапе содержит константный ключ. А конвертировать из структуры с константным ключем в структуру с неконстантным ключом.... Это копирование!...


И ни одного ворнинга! Мой же ты любимый С++.
👍8😐5🗿2🔥1👌1