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 regulator took order for the search and seizure operation from Judge Purushottam B Jadhav, Sebi Special Judge / Additional Sessions Judge. The company maintains that it cannot act against individual or group chats, which are “private amongst their participants,” but it will respond to requests in relation to sticker sets, channels and bots which are publicly available. During the invasion of Ukraine, Pavel Durov has wrestled with this issue a lot more prominently than he has before. Channels like Donbass Insider and Bellum Acta, as reported by Foreign Policy, started pumping out pro-Russian propaganda as the invasion began. So much so that the Ukrainian National Security and Defense Council issued a statement labeling which accounts are Russian-backed. Ukrainian officials, in potential violation of the Geneva Convention, have shared imagery of dead and captured Russian soldiers on the platform. Markets continued to grapple with the economic and corporate earnings implications relating to the Russia-Ukraine conflict. “We have a ton of uncertainty right now,” said Stephanie Link, chief investment strategist and portfolio manager at Hightower Advisors. “We’re dealing with a war, we’re dealing with inflation. We don’t know what it means to earnings.” At the start of 2018, the company attempted to launch an Initial Coin Offering (ICO) which would enable it to enable payments (and earn the cash that comes from doing so). The initial signals were promising, especially given Telegram’s user base is already fairly crypto-savvy. It raised an initial tranche of cash – worth more than a billion dollars – to help develop the coin before opening sales to the public. Unfortunately, third-party sales of coins bought in those initial fundraising rounds raised the ire of the SEC, which brought the hammer down on the whole operation. In 2020, officials ordered Telegram to pay a fine of $18.5 million and hand back much of the cash that it had raised. NEWS
from us


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