LINUX.ORG.RU

Размещение глобальных данных

 ,


0

1

Пример:

int x;
double y;
struct { char zs[256]; } z;

int main() {}

Регламентирует ли стандарт C(или C++) размещение x/y/z? Порядок, выравнивание и подобное. Если есть инфа по реализациям(гцц/шланг) - тоже интересно. Вариант просто собрать посмотреть не подойдёт - нужна какая-то стабильность/гарантии, а не просто текущее положение дел.

Ответ на: комментарий от qweururu

Там управляемая память будет.

Вот здесь я подвис. Это как?

Для управления нужно знать как и что там ложится.

Оно вообще по разным сегментам может раскидаться в зависимости от того как именно инициализируется.

bugfixer ★★★★★
()

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

потому в стандарте ему места быть не должно

alysnix ★★★
()

В общем, проблема не актуальна, в любом случае придётся пихать в структуру(потому что relocation truncated to fit: R_X86_64_PC32 against `.bss’ - это либо не исправить, либо как минимум в линковщик лезть - не хочу). Сделал так:

struct { ... }* data;
int main() {
  data = mmap(nullptr, sizeof(*data), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
}
qweururu
() автор топика

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

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

alysnix ★★★
()
Ответ на: комментарий от rumgot

Там ещё есть проблема, известная как «static initialization order fiasco», подход с функциями (или классами-синглтонами) используют главным образом из-за неё.

annulen ★★★★★
()
Ответ на: комментарий от wandrien

У топикстартера вообще в том числе и про C. А если вспомнить еще битовые поля и разный порядок байтов, то все неоднозначно. Как минимум надо покрыть тестами и статическими ассертами.

vromanov ★★★
()
Ответ на: комментарий от qweururu

Если задача требует знаний о размещении объектов в памяти, в линковщик лезть придётся. В эмбеддинге для этого существуют файлы Link Definition, обычно со своим языком, позволяющие точно задать адреса и размеры секций. Как с этим на писюках - я хз, но, возможно, тоже как-то выкручиваются.

alegz ★★★★
()
Ответ на: комментарий от wandrien

Да, с такими вводными данными именно возможно. А что тебя смущает? Чтобы знать точно, нужно брать конкретный случай и смотреть, что выдают разные компиляторы.

rumgot ★★★★★
()
Ответ на: комментарий от mittorn

Copilot отвечает так:


Компиляторы, соответствующие стандарту C++11 и выше, используют встроенные механизмы для обеспечения потокобезопасной инициализации статических локальных переменных. Эти механизмы включают в себя использование атомарных операций и других низкоуровневых примитивов синхронизации, а не явных мьютексов.

Основные моменты:

  1. Атомарные операции: Для проверки и инициализации статической локальной переменной компилятор может использовать атомарные операции, которые обеспечивают корректное поведение без необходимости в явных мьютексах.
  2. Блокировка на уровне компилятора: Стандарт C++11 обеспечивает потокобезопасную инициализацию, что позволяет компиляторам применять необходимые блокировки или проверки на уровне машинного кода.

Пример ассемблерного кода:

Как упоминалось ранее, проверка инициализации может быть реализована через сравнение и установку значений guard переменной. В данном случае мьютексы не используются, так как атомарные операции достаточны для гарантии корректного поведения:

f():
    push    rbp
    mov     rbp, rsp
    mov     rax, OFFSET FLAT:guard_variable  ; Загрузка адреса guard переменной
    cmp     BYTE PTR [rax], 0                ; Проверка инициализирован ли объект `a`
    jne     .Lalready_init                   ; Если объект инициализирован, переход к метке .Lalready_init

    ; Код инициализации переменной `a`
    mov     edi, OFFSET FLAT:f()::a
    call    A::A()                           ; Вызов конструктора для инициализации объекта `a`
    mov     BYTE PTR [rax], 1                ; Установка guard переменной в 1 (объект инициализирован)

.Lalready_init:
    mov     eax, OFFSET FLAT:f()::a          ; Загрузка адреса переменной `a`
    pop     rbp
    ret

Заключение:

Компиляторы используют эффективные методы, такие как атомарные операции, для обеспечения потокобезопасной инициализации статических локальных переменных. В большинстве случаев это происходит без явного использования мьютексов, что минимизирует накладные расходы и обеспечивает высокую производительность.

Если у вас есть дополнительные вопросы или требуется помощь с другими аспектами программирования, дайте знать!


Однако проверка инициализации все таки используется и оптимищатором не удаляется, поэтому товарищ @alysnix прав. Но даже с учетом такой проверки, мне это кажется лучше, чем инициализация всех глобальных переменных в один момент времени перед выполнением main.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

поэтому товарищ @alysnix прав

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

Возможно, но если мы сохраняем себе ссылку на что-то и храним, то нужно задаться вопросом: а нужна ли тут в принципе статическая глобальная переменная?

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Как минимум, msvc начинает обращаться к tls. Само по себе оно конечно не так медленно, но такой индиректный доступ явно медоеннее, чем код приведённый витвоём примере. Есть ли там mutex,
я не уточнял.

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

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

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Так делают не все компиляторы. Использование ссылок также на некоторых компиляторах ломает оптимизацию. Например, в в том, который используется у меня для переменных к которым нужен быстрый доступ выделяется сегмент размером в 64к и адрес середины этого сегмента всегда лежит в одном из регистров. И обращение происходит быстро т.к. в команде используется смещение относительно этого адреса. Не надо даже в регистр загружать 32-х битный адрес.

vromanov ★★★
()

Глобальные данные не нужны.

Если тебе нужны совместно используемые данные - протащи ссылку на них через контекст.

А за глобальные переменные я своим падаванам по рукам бил палкой на ревью, ибо нефиг. Равно и за шаренные без необходимости данные тоже.

no-dashi-v2 ★★★
()
Ответ на: комментарий от no-dashi-v2

Глобальные данные не нужны.

Это в которой из параллельных вселенных?

А за глобальные переменные я своим падаванам по рукам бил палкой на ревью

И как результат? Остался кто живой, или разбежались все?

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Это в которой из параллельных вселенных?

В любой. Глобальные и статические переменные это частный и неудачный случай разделяемых / совместно используемых данных, но при этом обладают кучей недостатков, начиная с проблемы полноценного тестирования и сайдэффектами. В нормальной ситуации вообще все шареднутые данные лучше прятать за методами доступа к ним, ибо нехрен

no-dashi-v2 ★★★
()
Ответ на: комментарий от no-dashi-v2

Читаю сейчас код на крестах…

У набора классов куча members, которые предназначены неявно передавать state между вызовами методов. Фактически, члены класса используются вместо аргументов при вызове методами друг друга.

Порядок работы кода жестко фиксирован через неявные связи, возникающие на этом разделяемом состоянии.

Часть членов класса имеет префикс m_, часть не имеет.

Сами методы длиннющие.

Такая коллекция антипаттернов, пипец.

Рефакторить это будет занятно…

wandrien ★★
()
Ответ на: комментарий от no-dashi-v2

Это в которой из параллельных вселенных?

В любой. Глобальные и статические переменные это частный и неудачный случай разделяемых / совместно используемых данных, но при этом обладают кучей недостатков

Что абсолютно не отменяет их необходимость и целесообразность. Классический пример - любая форма конфигурации. Вот нужно мне выкатить новый фунционал - у меня с огромной вероятностью будет boolean позволяющий эту логику отключить в случае необходимости без отката релиза софта. И я абсолютно точно не буду этот boolean таскать через 20 вызовов, это будет какой-нибудь static выставляющийся один раз в main() и использующийся read-only at the end use-points. И это только один из очевидных примеров.

В нормальной ситуации вообще все шареднутые данные лучше прятать за методами доступа к ним, ибо нехрен

Это как-то делает global statics чем то фундаментально другим?

bugfixer ★★★★★
()