Telegram Group & Telegram Channel
#dev #cpp

Вы все, конечно, знаете, что локальные функции в C++ нужно помечать словом static, потому что если не пометить, линкер ругнется на то, что у вас двойное определение:

// a.cpp
#include <cstdio>

void foo() {
puts("foo from a");
}

// b.cpp
#include <cstdio>

void foo() {
puts("foo from b");
}


/usr/bin/ld: /tmp/b-46f1fb.o: in function `foo()':
b.cpp:(.text+0x0): multiple definition of `foo()'; /tmp/a-2156f5.o:a.cpp:(.text+0x0): first defined here


Его можно понять, ведь все функции в C++ по умолчанию глобальны. Но сейчас я хочу на всякий случай напомнить, что static надо ставить не потому, что линкер ругается, а потому, что это UB, и как каждое другое UB, ODR violation ловится не всегда. Вот этот забавнейший кусок кода:

// a.cpp
#include <cstdio>

__attribute__((noinline))
inline void foo() {
puts("foo from a");
}

__attribute__((always_inline))
inline void bar() {
puts("bar from a");
}

void a() {
foo();
bar();
}

// b.cpp
/* то же самое с b вместо a */

// main.cpp
void a(void);
void b(void);

int main() {
a();
b();
}


...компилируется без ворнингов на -Wextra и выводит

foo from a
bar from a
foo from a
bar from b


Если вы не в курсе, механизм компиляции inline-функций немного необычный. inline — это не про инлайнинг (для этого есть атрибуты), а исключительно выключалка для проверки ODR. Каждая единица трансляции собирает свою копию inline-функции, а в итоговом исполняемом файле они "склеиваются", и остается только одна копия. bar помечена как always_inline, поэтому она резолвится внутри единицы трансляции, а вот вызов в foo остается и приводит к необычным эффектам.

Ухудшает ситуацию то, что inline-функции люди пишут не то чтоб прям часто, а вот шаблонные функции, которые тоже неявно inline, чтобы можно было засунуть реализацию в заголовочный файл — каждый день. То есть если у вас вдруг каким-то чудом в двух библиотеках авторы забыли сделать локальную шаблонную функцию map статической, вам будет больно. Ну или не будет, если одна из них достаточно мелкая, чтобы заинлайниться.

Еще бывает прикольно, когда функция вроде как одна, но собирается в разных местах с разными #defineами. На первый взгляд ODR violation нет, на второй — вы начинаете интерпретировать UCS-2 текст как ASCII.

Весело, в общем.

В продолжение этой катавасии идут локальные шаблонные классы с generic названиями в духе Node, авторы которых не подумали, что конструктор, да и все другие методы, надо бы сделать статическими:

// a.cpp
#include <cstdio>

template<typename T>
struct Foo {
int field;

Foo() {
puts("construct a");
}
};

void a() {
Foo<int> tmp;
}

// b.cpp
/* то же самое с b вместо a */

// main.cpp
void a(void);
void b(void);

int main() {
a();
b();
}


construct a
construct a


Но static в классах означает совсем другое, поэтому вместо него надо использовать анонимные namespaceы. Если класс обернуть в inline namespace { ... }, то проблема уйдет. Функции, кстати, тоже можно туда засунуть, вместо того, чтобы писать static. Вроде хорошая идея, но потом вы случайно создадите анонимный namespace внутри заголовочного файла, и вашему ICFу придется работать в двадцать раз сильнее.

Можно еще использовать именованные пространства имен, это что-то в духе золотой середины, только надо понимать, что уникальность имен namespace'ов никто не проверяет, а популярные либы любят брать себе generic имена типа testing (looking at you, Google Test), так что вам придется взять себе в качестве названия UUID.

Размышления о том, почему на дворе 2025 год, а namespace { ... } нужно писать даже с модулями (хотя ODR violation они ловят), оставлю за кадром.



group-telegram.com/alisa_rummages/205
Create:
Last Update:

#dev #cpp

Вы все, конечно, знаете, что локальные функции в C++ нужно помечать словом static, потому что если не пометить, линкер ругнется на то, что у вас двойное определение:

// a.cpp
#include <cstdio>

void foo() {
puts("foo from a");
}

// b.cpp
#include <cstdio>

void foo() {
puts("foo from b");
}


/usr/bin/ld: /tmp/b-46f1fb.o: in function `foo()':
b.cpp:(.text+0x0): multiple definition of `foo()'; /tmp/a-2156f5.o:a.cpp:(.text+0x0): first defined here


Его можно понять, ведь все функции в C++ по умолчанию глобальны. Но сейчас я хочу на всякий случай напомнить, что static надо ставить не потому, что линкер ругается, а потому, что это UB, и как каждое другое UB, ODR violation ловится не всегда. Вот этот забавнейший кусок кода:

// a.cpp
#include <cstdio>

__attribute__((noinline))
inline void foo() {
puts("foo from a");
}

__attribute__((always_inline))
inline void bar() {
puts("bar from a");
}

void a() {
foo();
bar();
}

// b.cpp
/* то же самое с b вместо a */

// main.cpp
void a(void);
void b(void);

int main() {
a();
b();
}


...компилируется без ворнингов на -Wextra и выводит

foo from a
bar from a
foo from a
bar from b


Если вы не в курсе, механизм компиляции inline-функций немного необычный. inline — это не про инлайнинг (для этого есть атрибуты), а исключительно выключалка для проверки ODR. Каждая единица трансляции собирает свою копию inline-функции, а в итоговом исполняемом файле они "склеиваются", и остается только одна копия. bar помечена как always_inline, поэтому она резолвится внутри единицы трансляции, а вот вызов в foo остается и приводит к необычным эффектам.

Ухудшает ситуацию то, что inline-функции люди пишут не то чтоб прям часто, а вот шаблонные функции, которые тоже неявно inline, чтобы можно было засунуть реализацию в заголовочный файл — каждый день. То есть если у вас вдруг каким-то чудом в двух библиотеках авторы забыли сделать локальную шаблонную функцию map статической, вам будет больно. Ну или не будет, если одна из них достаточно мелкая, чтобы заинлайниться.

Еще бывает прикольно, когда функция вроде как одна, но собирается в разных местах с разными #defineами. На первый взгляд ODR violation нет, на второй — вы начинаете интерпретировать UCS-2 текст как ASCII.

Весело, в общем.

В продолжение этой катавасии идут локальные шаблонные классы с generic названиями в духе Node, авторы которых не подумали, что конструктор, да и все другие методы, надо бы сделать статическими:

// a.cpp
#include <cstdio>

template<typename T>
struct Foo {
int field;

Foo() {
puts("construct a");
}
};

void a() {
Foo<int> tmp;
}

// b.cpp
/* то же самое с b вместо a */

// main.cpp
void a(void);
void b(void);

int main() {
a();
b();
}


construct a
construct a


Но static в классах означает совсем другое, поэтому вместо него надо использовать анонимные namespaceы. Если класс обернуть в inline namespace { ... }, то проблема уйдет. Функции, кстати, тоже можно туда засунуть, вместо того, чтобы писать static. Вроде хорошая идея, но потом вы случайно создадите анонимный namespace внутри заголовочного файла, и вашему ICFу придется работать в двадцать раз сильнее.

Можно еще использовать именованные пространства имен, это что-то в духе золотой середины, только надо понимать, что уникальность имен namespace'ов никто не проверяет, а популярные либы любят брать себе generic имена типа testing (looking at you, Google Test), так что вам придется взять себе в качестве названия UUID.

Размышления о том, почему на дворе 2025 год, а namespace { ... } нужно писать даже с модулями (хотя ODR violation они ловят), оставлю за кадром.

BY Алиса копается


Warning: Undefined variable $i in /var/www/group-telegram/post.php on line 260

Share with your friend now:
group-telegram.com/alisa_rummages/205

View MORE
Open in Telegram


Telegram | DID YOU KNOW?

Date: |

The Dow Jones Industrial Average fell 230 points, or 0.7%. Meanwhile, the S&P 500 and the Nasdaq Composite dropped 1.3% and 2.2%, respectively. All three indexes began the day with gains before selling off. Lastly, the web previews of t.me links have been given a new look, adding chat backgrounds and design elements from the fully-features Telegram Web client. The Security Service of Ukraine said in a tweet that it was able to effectively target Russian convoys near Kyiv because of messages sent to an official Telegram bot account called "STOP Russian War." WhatsApp, a rival messaging platform, introduced some measures to counter disinformation when Covid-19 was first sweeping the world. READ MORE
from us


Telegram Алиса копается
FROM American