Как обойти строгий псевдоним в C?

Моя цель — создать универсальный распределитель арены с буфером, хранящимся в разделе .bss исполняемого файла, чтобы избежать каких-либо выделений в реальной программе, однако в C есть проблемы со строгим псевдонимом. Если я определяю буфер как static char buffer[BUFFER_SIZE];, то из-за строгого псевдонима я не смогу использовать на него указатели другого типа, не вызывая неопределенного поведения. Поскольку я хочу, чтобы этот распределитель был универсальным, мне нужно иметь возможность выделять в нем любой тип.

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

Единственный известный мне способ обойти эту проблему в C — полностью отключить строгое псевдонимирование с помощью флага -fno-strict-aliasing. В этом сообщении блога автор пытается сделать то же самое, но приходит решение — использовать встроенную сборку для «отмывания» указателя, чтобы оптимизатор не мог применить строгую оптимизацию псевдонимов, но это кажется очень хрупким. .

Есть ли лучший способ сделать что-то подобное? В идеале должна быть встроенная функция, позволяющая четко обозначить приведение или область памяти, которые будут игнорироваться строгим псевдонимом.

Я думал, что charосвобожден от строгого правила псевдонимов?

Thomas 16.03.2024 11:16

@Thomas Да, но только если это тип, используемый для доступа, а не исходный тип. Вы можете использовать псевдоним float* на char*, но не наоборот, что я здесь и делаю. На самом деле ответ, на который вы ссылаетесь, объясняет это: «[...] Однако по-другому это не сработает: нет предположения, что ваша структура создает псевдоним буфера символов».

bakaq 16.03.2024 11:20

Эта ссылка gcc с доступом по указателю, похоже, описывает несоответствующий компилятор. Соответствующий компилятор должен следовать C17 6.7.2.1 §16, выделено мной: «Значение не более одного из членов может быть сохранено в объекте объединения в любое время. Указатель на объект объединения, преобразованный соответствующим образом, указывает на каждый его членов (или, если член является битовым полем, то к единице, в которой он находится), и наоборот». Если компилятор предполагает, что int* нельзя использовать для доступа к объединению, содержащему член int, то это нарушает вышеуказанное требование стандарта C.

Lundin 18.03.2024 10:58

Кроме того, вышеуказанному требованию соответствует само «правило строгого псевдонимов» (6.5). «Объект должен иметь доступ к сохраненному значению только с помощью выражения lvalue, которое имеет один из следующих типов:.. /-/ ...» тип агрегата или объединения, который включает в себя один из вышеупомянутых типов [тип, совместимый с эффективным тип объекта] среди его членов"

Lundin 18.03.2024 11:26

Возможно, документация gcc путает поведение C и поведение C++. В общем, документация gcc имеет скверную репутацию, и ей нельзя доверять. Если только реальный компилятор (gcc в режиме C) не ведет себя так, как указано в документации.

Lundin 18.03.2024 11:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
203
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Поместите код выделения в отдельный модуль или модули из кода, использующего его (клиентский код). Затем, пока вы выполняете сборку с использованием традиционных компиляторов и компоновщиков, которые не выполняют межмодульную оптимизацию, псевдонимы обязательно работают из-за природы отдельных модулей инструментов сборки, а не из-за требований стандарта C. (Сам код выделения должен соблюдать правила псевдонимов.)

Причина, по которой это работает, заключается в том, что при компиляции некоторого клиентского кода подпрограмм выделения компилятор видит только клиентский код, а не код выделения. Он не может знать объявленный тип, используемый кодом выделения. После компиляции клиентского кода вы можете связать его с вашими процедурами выделения или, гипотетически, связать его с каким-либо другим кодом, в котором все адреса, возвращаемые кодом выделения, имеют объявленные типы, соответствующие их использованию, или с каким-либо другим кодом, в котором все адреса, возвращаемые кодом распределения, предоставляются malloc, определенным стандартом C, а не вашим собственным кодом. Поскольку клиентский код может быть связан с этим другим кодом, компилятор должен создать объектный модуль, который работает с ним, как указано в стандарте C. Однако поведение вашего кода выделения и поведение этого другого кода идентично с точки зрения интерфейсов между ними: клиентский код вызывает процедуру выделения и получает указатель, или он вызывает процедуру освобождения и предоставляет указатель, и так на. Следовательно, когда компилятор генерирует код, который работает с другим кодом, соответствующим стандарту, он также должен работать с вашим фактическим кодом выделения.

Это действительно гениально! Я чувствую, что это все еще подпадает под категорию «обмана компилятора», как и способ встроенной сборки, но он кажется гораздо более устойчивым и переносимым. Проблема в том, что для этого требуется иметь более одной единицы перевода, и я думаю, не следует использовать LTO при связывании этого модуля, что усложняет сборку.

bakaq 16.03.2024 11:42

В некоторых случаях наличие в библиотеке распределения копии последнего освобожденного объекта и ее использование для удовлетворения следующего запроса на выделение может быть полезной оптимизацией, а преимущества можно увеличить за счет использования LTO; это может потерпеть неудачу, если не использовать -fno-strict-aliasing, но выгода от LTO может превысить стоимость -fno-strict-aliasing.

supercat 16.03.2024 21:21

«пока вы строите с использованием традиционных компиляторов и компоновщиков, которые не выполняют межмодульную оптимизацию» - это становится все труднее обеспечить с помощью современных цепочек инструментов.

Nate Eldredge 17.03.2024 20:43

TLDR: хотя флаг -fno-strict-aliasing официально не признан «стандартом», он более широко поддерживается и надежнее, чем любые альтернативные конструкции, кроме принудительного разделения единиц компиляции и блокировки оптимизации времени компоновки, а затраты на производительность -fno-strict-aliasing часто будут меньше, чем наложено. непрозрачными вызовами функций.

Используйте флаг -fno-strict-aliasing с clang или gcc без извинений. Это наиболее портативный и надежный способ заставить такой код работать. Согласно опубликованному обоснованию, авторы стандарта C намеревались, чтобы реализации - на основе «качества реализации» - расширяли семантику языка, определяя поведение большего количества конструкций и крайних случаев, чем предусмотрено стандартом. в тех случаях, когда это было бы полезно. Они стремились дать программистам «боевой шанс» (их слова!) для написания переносимых программ, но никогда не предполагали, что программисты прыгнут через обручи, чтобы обеспечить переносимость, за исключением случаев, когда их код должен быть пригоден для использования в конкретной реализации, которая иначе не могла бы его поддерживать. . Написание кода для использования -fno-strict-aliasing без какого-либо синтаксиса, специфичного для компилятора, даст программу, совместимую с любым компилятором, имеющим режим, эквивалентный -fno-strict-aliasing, то есть почти с любым компилятором C общего назначения на планете.

Если не использовать -fno-strict-aliasing, некоторые части оптимизаторов clang или gcc могут преобразовать код, поведение которого должен был определить стандарт, в код, который последующие оптимизации не могут обрабатывать значимо. Если, например, какой-то код считывает значение с использованием 64-битного long, повторно использует хранилище как 64-битное long long, а затем использует long long для записи значения, которое численно совпадает с прочитанным long, clang и gcc могут рассматривать последнее действие как возврат эффективного типа хранилища к long. Хотя clang или gcc не распознают и не делают ничего странного в подавляющем большинстве ситуаций, когда значение, записанное в хранилище с использованием одного типа, совпадает со значением, которое было прочитано с использованием другого типа, Стандарт не делает различий между случаями, когда они не будут применять такие преобразования и случаи, когда они явно применяют их способами, которые что-то ломают.

Хотя можно написать некоторые конструкции «неоптимизируемым» способом, это часто приводит к тому, что машинный код становится менее эффективным, чем то, что можно было бы достичь с помощью -fno-strict-aliasing, а также к ряду ситуаций, когда особенности в будущих версиях clang и gcc могут сломаться. вещи будут сокращены.

Проблема с компиляторами с открытым исходным кодом заключается в том, что они, похоже, не стремятся стать/оставаться качественной реализацией. У коммерческих составителей есть такие амбиции, поскольку они хотят иметь клиентов. В то время как компиляторы с открытым исходным кодом довольствуются тем, что являются «станцией смерти, соответствующей требованиям ISO», потому что у них есть доступ к множеству языковых юристов, но не столько к людям, использующим компилятор в контексте, отличном от ПК, например, во встроенных системах, где строгие правила псевдонимов не имеют смысла ни в том виде, в котором они написаны, ни в том виде, в котором они задуманы.

Lundin 18.03.2024 11:15

Например, строгие обоснования псевдонимов часто приводят к примеру, когда функция, принимающая параметр int*, должна иметь возможность предположить, что этот указатель не обращается к какой-либо внешней связи double, и наоборот. Однако даже это обоснование не является разумным. ->

Lundin 18.03.2024 11:17

Существует множество встроенных систем, в которых вам необходимо хранить общие данные во флэш-памяти/eeprom или сериализовать/десериализовать данные для отправки по различным шинам. Будут определенные требования к выравниванию и порядку байтов, не обязательно совпадающие с фундаментальным выравниванием ЦП. Возможно, ему придется работать с потоками байтов. Таким образом, всегда существует необходимость доступа к переменным, используемым для такого рода аппаратного программирования, через тип, отличный от объявленного. Вот как работает компьютерное оборудование. Рабочая группа ISO C должна в конечном итоге признать это.

Lundin 18.03.2024 11:17

@Lundin: У вас есть типы Rationale наоборот: функция принимала параметр double* и обращалась к нему, а также к отдельному объекту int, что имело бы смысл только на платформах, где int был по крайней мере такого же размера, как double. Что действительно необходимо, так это признать, что это правило было предназначено только для того, чтобы позволить компиляторам предполагать, что вещи не будут псевдонимами, когда не было особых доказательств того, что это возможно. Если бы компиляторы приложили добросовестные усилия, чтобы заметить такие доказательства, этого было бы достаточно в 99% случаев, и если бы программисты позаботились о предоставлении доказательств в тех случаях, когда компиляторы могли их пропустить...

supercat 18.03.2024 16:01

... это могло бы позаботиться обо всем остальном. Чрезвычайно подавляющее большинство случаев, которые в настоящее время требуют -fno-strict-aliasing, делают это потому, что авторы clang и gcc используют Стандарт как оправдание своей несовместимости с устоявшимися конструкциями, которые компиляторы, по сути, единогласно поддерживали раньше.

supercat 18.03.2024 16:03

Я бы не сказал, что clang и gcc не претендуют на роль «качественных реализаций», поскольку авторы ошибочно интерпретируют Стандарт как определяющий идеализированную форму языка, к которой должны стремиться все программы, а не минимальную форму. это должны принять даже компиляторы самого низкого качества и, таким образом, думать, что они могут улучшить язык, если будут препятствовать использованию «непереносимых» конструкций. Рассматривать доступ к *(unsigned*)floatPtr как потенциальный доступ к float не составит труда и не ухудшит производительность любых неизобретенных программ, но это будет...

supercat 19.03.2024 17:11

@Lundin: (см. выше) ... признать законность выполнения каламбура таким образом, вместо использования «переносимых» подходов, таких как объединения, которые - не случайно - они обрабатывают более эффективно, чем многие коммерческие компиляторы (которые вместо этого фокусируются на правильная обработка более просто написанного кода).

supercat 19.03.2024 17:16

"думают, что они смогут улучшить язык, если будут препятствовать использованию "непереносимых" конструкций" Серьезно? Мне кажется, что каждую неделю или около того выпускается какое-то новое, совершенно бесполезное расширение GNU. В идеале это прямо противоречит требованиям того, насколько строго должны вести себя соответствующие программы, чтобы неоднократно лишить программистов уверенности в использовании этого инструмента в любом профессиональном контексте. Последней стабильной версией была gcc версии 7 или что-то в этом роде. Это печальное положение дел.

Lundin 19.03.2024 22:12

@Lundin: Я не говорю, что сторонники clang и gcc последовательны в своей философии, но сопротивление поддержке идиоматических каламбурных конструкций, похоже, вызвано идеологией, а не желанием максимизировать полезность.

supercat 19.03.2024 22:19

Другие вопросы по теме