C++ и интероп
От: cppguard  
Дата: 21.10.24 00:47
Оценка:
Допустим, есть некоторый протокол, для которого определены структуры данных. Раньше можно было описать их на Си, затем подключать полученный заголовочный файл, куда нужно — хочешь, генерируй обёртки для Java, хочешь — для Python, а можешь сразу в С++ использовать. Сейчас же Си и С++ довольно далеко разошлись. Настолько, что простой union из нескольких полей и равного по размеру массива (типичное представление вектора в Си) может привести к UB. И что делать? То ли сразу на С++ писать (std::variant вместо union, std::array вместо []), теряя возможность легко и непринуждённо генерировать bindings в другие языки, то ли оставаться на сях, снова и снова привнося ложечку дряхлости в современный в остальных аспектах код на С++

P.S. Я знаю про swig, но он слишком тяжёлый для моих целей, да и код генерирует многословный.
Re: C++ и интероп
От: kov_serg Россия  
Дата: 21.10.24 08:58
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Допустим, есть некоторый протокол, для которого определены структуры данных. Раньше можно было описать их на Си, затем подключать полученный заголовочный файл, куда нужно — хочешь, генерируй обёртки для Java, хочешь — для Python, а можешь сразу в С++ использовать. Сейчас же Си и С++ довольно далеко разошлись. Настолько, что простой union из нескольких полей и равного по размеру массива (типичное представление вектора в Си) может привести к UB. И что делать? То ли сразу на С++ писать (std::variant вместо union, std::array вместо []), теряя возможность легко и непринуждённо генерировать bindings в другие языки, то ли оставаться на сях, снова и снова привнося ложечку дряхлости в современный в остальных аспектах код на С++


C>P.S. Я знаю про swig, но он слишком тяжёлый для моих целей, да и код генерирует многословный.


http://kaitai.io ?
Re: C++ и интероп
От: Pzz Россия https://github.com/alexpevzner
Дата: 21.10.24 09:04
Оценка: +3
Здравствуйте, cppguard, Вы писали:

C>Допустим, есть некоторый протокол, для которого определены структуры данных. Раньше можно было описать их на Си, затем подключать полученный заголовочный файл, куда нужно — хочешь, генерируй обёртки для Java, хочешь — для Python, а можешь сразу в С++ использовать. Сейчас же Си и С++ довольно далеко разошлись. Настолько, что простой union из нескольких полей и равного по размеру массива (типичное представление вектора в Си) может привести к UB.


Это всегда было UB. Но на практике работало. Потому, что все понимали, что есть 100500 системного кода, который на это рассчитывает.

Так что в принципе, ничего не изменилось. Можно как и раньше, описать сишную структуру и надеяться, что с #pragma pack (1) компилятор не будет умничать.

C>И что делать? То ли сразу на С++ писать (std::variant вместо union, std::array вместо []), теряя возможность легко и непринуждённо генерировать bindings в другие языки, то ли оставаться на сях, снова и снова привнося ложечку дряхлости в современный в остальных аспектах код на С++


Можно сделать полноценый маршалинг. Т.е., считаем внешнее представление последовательностью байтов, руками собираем, руками разбираем.

В принципе, нонешние компиляторы смышленые, итоговый машинный код может получиться не сильно хуже, чем в случае со структурами. Исходные тексты, конечно, читать-писать в таком стиле будет более муторно.
Re: C++ и интероп
От: пффф  
Дата: 21.10.24 09:21
Оценка: +1
Здравствуйте, cppguard, Вы писали:

C>Допустим, есть некоторый протокол, для которого определены структуры данных. Раньше можно было описать их на Си, затем подключать полученный заголовочный файл, куда нужно — хочешь, генерируй обёртки для Java, хочешь — для Python, а можешь сразу в С++ использовать. Сейчас же Си и С++ довольно далеко разошлись. Настолько, что простой union из нескольких полей и равного по размеру массива (типичное представление вектора в Си) может привести к UB.


Ерунда какая-то. Всё работает как и раньше
Re: C++ и интероп
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 21.10.24 21:24
Оценка:
Здравствуйте, cppguard, Вы писали:

C>И что делать?


Навесить на все это assert'ов (статических и динамических), и напрягаться только в случае срабатывания. В 99.9% не сработают.
Re[2]: C++ и интероп
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 22.10.24 06:27
Оценка: 6 (1)
Здравствуйте, Pzz, Вы писали:

C>>Допустим, есть некоторый протокол, для которого определены структуры данных. Раньше можно было описать их на Си, затем подключать полученный заголовочный файл, куда нужно — хочешь, генерируй обёртки для Java, хочешь — для Python, а можешь сразу в С++ использовать. Сейчас же Си и С++ довольно далеко разошлись. Настолько, что простой union из нескольких полей и равного по размеру массива (типичное представление вектора в Си) может привести к UB.

Pzz>Это всегда было UB. Но на практике работало. Потому, что все понимали, что есть 100500 системного кода, который на это рассчитывает.

Ещё можно явно барьеры компилятору ставить через atomic_signal_fence().
Записал в одно поле union, поставил барьер, прочитал другое поле.
Оптимизация чуть пострадает, но для задачи разбора таких структур, кажется, без разницы, затраты на сеть будут больше.
Или WRITE_ONCE + READ_ONCE образца Linux (которые внутри превращаются в volatile-доступы).

Можно для одного файла (или даже функции, GCC умеет) сократить оптимизации.

В общем, методы ещё есть. Пока не все зарезали

Pzz>Можно сделать полноценый маршалинг. Т.е., считаем внешнее представление последовательностью байтов, руками собираем, руками разбираем.


И это тоже.

Pzz>В принципе, нонешние компиляторы смышленые, итоговый машинный код может получиться не сильно хуже, чем в случае со структурами. Исходные тексты, конечно, читать-писать в таком стиле будет более муторно.


Опять же, по сравнению с затратами на диск и сеть это копеечное время.
А если где-то реально становится узким местом... таких немного, их можно и специально обработать.
The God is real, unless declared integer.
Отредактировано 22.10.2024 6:38 netch80 . Предыдущая версия .
Re[3]: C++ и интероп
От: Pzz Россия https://github.com/alexpevzner
Дата: 22.10.24 07:46
Оценка:
Здравствуйте, netch80, Вы писали:

N>Ещё можно явно барьеры компилятору ставить через atomic_signal_fence().

N>Записал в одно поле union, поставил барьер, прочитал другое поле.
N>Оптимизация чуть пострадает, но для задачи разбора таких структур, кажется, без разницы, затраты на сеть будут больше.
N>Или WRITE_ONCE + READ_ONCE образца Linux (которые внутри превращаются в volatile-доступы).

Мне как-то даже в голову не приходило делать это через volatile (или эквиавалент).

В принципе, правила этой весёлой игры говорят, что если кастировать через (char*), компилятор должен понять намёк и перечитывать поля.

N>Опять же, по сравнению с затратами на диск и сеть это копеечное время.

N>А если где-то реально становится узким местом... таких немного, их можно и специально обработать.

Я как-то разбирался, почему на какой-то армовской (или PPC? уже не помню...) железке с линухом внутри сеть медленно работает. Оказалось, какой-то альтернативно одаренный (1) включил опцию в ядре, которая ловит исключения по невыровненному доступу к 32-битным данным и эмулироет поведение в стиле x86 (2) не учел, что в заголовке ethernet 14 байт, в результате все IP-заголовки были выровнены на 4N + 2 байта.

Просадка скорости была в два раза. Как только я выключил эмуляцию невыровненного доступа, мне ядро сразу напечатало стек, показав пальцем на источник проблемы, ну а дальше было уже совсем просто.
Re[3]: C++ и интероп
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 22.10.24 08:20
Оценка:
Здравствуйте, netch80, Вы писали:

N>можно явно барьеры компилятору ставить через atomic_signal_fence().


Это барьер для аппаратуры — зачем ставить именно его, если нет речи о многопоточной параллельной обработке? Если ставить, то барьеры сугубо для компилятора. Тот же volatile, для начала.
Re[4]: C++ и интероп
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 23.10.24 09:13
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

N>>можно явно барьеры компилятору ставить через atomic_signal_fence().


ЕМ>Это барьер для аппаратуры — зачем ставить именно его, если нет речи о многопоточной параллельной обработке? Если ставить, то барьеры сугубо для компилятора. Тот же volatile, для начала.


Ты перепутал atomic_signal_fence с atomic_thread_fence. Прочитай про оба.
The God is real, unless declared integer.
Re[4]: C++ и интероп
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 23.10.24 09:54
Оценка:
Здравствуйте, Pzz, Вы писали:

N>>Ещё можно явно барьеры компилятору ставить через atomic_signal_fence().

N>>Записал в одно поле union, поставил барьер, прочитал другое поле.
N>>Оптимизация чуть пострадает, но для задачи разбора таких структур, кажется, без разницы, затраты на сеть будут больше.
N>>Или WRITE_ONCE + READ_ONCE образца Linux (которые внутри превращаются в volatile-доступы).
Pzz>Мне как-то даже в голову не приходило делать это через volatile (или эквиавалент).

Ну вот странно, потому что самый прямой вариант

Pzz>В принципе, правила этой весёлой игры говорят, что если кастировать через (char*), компилятор должен понять намёк и перечитывать поля.


Насколько я понял стандарт, не так. Доступ через char* просто никогда не алиасится с другим таким же доступом через char*. Но это не мешает оптимизировать доступ к тому же адресу через другие указатели. Берём код:

void foo(int *x, char *c)
{
  *x = 1;
  *c = 0;
  *x = 2;
}


Компилируем gcc с -O2:

foo:
        movb    $0, (%rsi)
        movl    $2, (%rdi)
        ret


Первая запись в *x съедена оптимизацией.

N>>Опять же, по сравнению с затратами на диск и сеть это копеечное время.

N>>А если где-то реально становится узким местом... таких немного, их можно и специально обработать.

Pzz>Я как-то разбирался, почему на какой-то армовской (или PPC? уже не помню...) железке с линухом внутри сеть медленно работает. Оказалось, какой-то альтернативно одаренный (1) включил опцию в ядре, которая ловит исключения по невыровненному доступу к 32-битным данным и эмулироет поведение в стиле x86 (2) не учел, что в заголовке ethernet 14 байт, в результате все IP-заголовки были выровнены на 4N + 2 байта.


Pzz>Просадка скорости была в два раза. Как только я выключил эмуляцию невыровненного доступа, мне ядро сразу напечатало стек, показав пальцем на источник проблемы, ну а дальше было уже совсем просто.


Вообще в современных процессорах все доступы кроме атомарных стараются допускать невыровненными. Затраты на это копеечные, пользы достаточно много.
Но пример, да, показательный.
The God is real, unless declared integer.
Re[5]: C++ и интероп
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 23.10.24 11:56
Оценка:
Здравствуйте, netch80, Вы писали:

N>Ты перепутал atomic_signal_fence с atomic_thread_fence. Прочитай про оба.


Так я читал. Само то, что оно "atomic", означает, что это относится к параллельному исполнению, а не последовательному. В обсуждаемой ситуации оба могут давать только побочный эффект.
Re[6]: C++ и интероп
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 23.10.24 12:58
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, netch80, Вы писали:


N>>Ты перепутал atomic_signal_fence с atomic_thread_fence. Прочитай про оба.


ЕМ>Так я читал. Само то, что оно "atomic", означает, что это относится к параллельному исполнению, а не последовательному.


Для signal это параллельное не по отношению к другому треду или ядру, а по отношению к возможному визиту прерывания (для "сигнала" транслированного ядром на userland, но тем не менее приходящему в непредсказуемые моменты).

EM> В обсуждаемой ситуации оба могут давать только побочный эффект.


Для данной задачи важно то, что в этом случае компилятор не может залагаться на то, что данные снаружи тех, что он гарантированно контролирует, как чисто локальные переменные, не изменились — и потому он не может кэшировать их в регистрах, на стеке или где ещё он будет хранить свои временные копии.
The God is real, unless declared integer.
Re: C++ и интероп
От: Pavel Dvorkin Россия  
Дата: 04.11.24 12:54
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Допустим, есть некоторый протокол, для которого определены структуры данных. Раньше можно было описать их на Си, затем подключать полученный заголовочный файл, куда нужно — хочешь, генерируй обёртки для Java, хочешь — для Python, а можешь сразу в С++ использовать. Сейчас же Си и С++ довольно далеко разошлись. Настолько, что простой union из нескольких полей и равного по размеру массива (типичное представление вектора в Си) может привести к UB. И что делать? То ли сразу на С++ писать (std::variant вместо union, std::array вместо []), теряя возможность легко и непринуждённо генерировать bindings в другие языки, то ли оставаться на сях, снова и снова привнося ложечку дряхлости в современный в остальных аспектах код на С++


ИМХО если писать на C++ в old school стиле, то совместимость с С будет полная. Иначе просто непонятно, как будут взаимодействовать C и C++, а проектов таких много.
With best regards
Pavel Dvorkin
Re[2]: C++ и интероп
От: cppguard  
Дата: 05.11.24 07:25
Оценка: +1
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>ИМХО если писать на C++ в old school стиле, то совместимость с С будет полная. Иначе просто непонятно, как будут взаимодействовать C и C++, а проектов таких много.


Это понятно =) Но мне Си не нужен. Я уважаю этот язык, но его время прошло. На современном С++ можно писать более безопасный код, ни в чём не проигрывая.
Re[3]: C++ и интероп
От: Pavel Dvorkin Россия  
Дата: 05.11.24 08:47
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Это понятно =) Но мне Си не нужен. Я уважаю этот язык, но его время прошло. На современном С++ можно писать более безопасный код, ни в чём не проигрывая.


Так никто не заставляет все писать в этом стиле. Только то, что связано с интеропом. Там все равно придется писать в C-like стиле.
With best regards
Pavel Dvorkin
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.