4927 subscribers

Микроконтроллеры для начинающих. Часть 28. Мост к Си. Размещение в памяти. Секции, сегменты, особенности.

943 full reads
3k story viewsUnique page visitors
943 read the story to the endThat's 31% of the total page views
4,5 minutes — average reading time

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

Как я уже говорил, я не рассчитываю, что вы будете писать программы на ассемблере. Наиболее популярными языками используемым при разработке программ для микроконтроллеров являются С и С++. С точки зрения микроконтроллеров, а цикл статей все таки именно о них, принципиальной разницы между С и С++ нет. Хотя для программиста, безусловно, есть весьма ощутимая разница. Что бы не загромождать суть рассматриваемых вопросов тонкостями С++ (который достоин отдельного рассмотрения) я буду в дальнейшем использовать С, за редким исключением. При этом сам С будет не целью, а лишь средством.

Хочу сразу обратить внимание пуристов и блюстителей чистоты и стандартов С/С++, а так же профессионалов использующих эти языки при разработке прикладных программ для универсальных ЭВМ, что в статьях обсуждается не сам язык, а его практическая реализация в компиляторах для микроконтроллеров. Примеры программ выбираются с учетом максимальной наглядности применительно к темам статей, а не с целью максимально показать возможности и мощь языка. Причем речь идет только о 8-битных микроконтроллерах.

Начнем мы с рассмотрения того, как наши программы размещаются в физической памяти микроконтроллера. И какое влияние это оказывает на сами программы. Внимательные читатели могут сказать "позвольте, но мы же уже изучали в статьях:

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

На самом деле, мы уже касались одного дополнительного логического уровня абстракции расположенного над физическим уровнем. Вспомните, что во многих микроконтроллерах есть единое адресное пространство памяти где память программ и память данных просто представлены разными областями.
Особенности использования памяти тесно связаны с процессом компиляции и сборки (компоновки) программы. А значит и с используемым средством разработки программ. Превращение программы из набора текстовых файлов в машинные команды и двоичный код мы будем рассматривать подробнее отдельно.

Что бы разговор был предметным нам нужен фрагмент программы для изучения

"Подопытный" фрагмент программы на С
"Подопытный" фрагмент программы на С
"Подопытный" фрагмент программы на С

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

Первый взгляд на составляющие программу части

Разумеется, каждая программа выполняет какие то действия над какими то данными. Даже если программа не делает ничего, как в нашем примере. Именно на этом факте основано разделение физической памяти в Гарвардской архитектуре на два основных типа - память программ и память данных. Но нас интересует логическая структура.

Выполняемый код программы

На первый взгляд, выполняемым кодом в нашем примере является функция func. Но это не совсем так. Во первых, до начала выполнения func выполняются некоторые действия. Это верно даже для функции main, которая в С представляет собой основную программу. И некоторые действия выполняются после завершения func.

Во вторых, некоторые строки func не выполняются внутри собственно func. Такой строкой является строка 6

static int local_static_var=64;

Вам может показаться странным, но присваивание выполняется совсем не при вызове func. И мы скоро с этим разберемся.

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

В универсальных ЭВМ может быть реализована аппаратная поддержка ограничения видимости и разграничения доступа. В нашем же случае это входит в зону ответственности компилятора и не сказывается на размещении кода в памяти программ.

Кроме того, в микроконтроллерах выполняемый код размещается в ПЗУ, а значит нет возможности динамически, во время выполнения программы, создавать и уничтожать процедуры. Да, я прекрасно помню, что возможность записи в память программ во время выполнения программы все таки есть, пусть и не во всех микроконтроллерах. Но это особый, и довольно не тривиальный, случай. Поэтому можно считать, что выполняемый код не просто существует все время работы программы, но и является неизменным.

Переменные

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

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

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

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

* Тип размещения auto

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

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

В нашем примере есть три локальных переменные. local_var_1 и local_var_2 существуют все время выполнения func. Они могут быть скрыты во вложенных областях видимости (блоках {}), но существовать при этом не перестают. Переменная local_var_3 существует только внутри блока определяемого циклом do while.

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

Получить адрес (ссылку) локальной переменной можно. Но вот использование этого адреса несет неявные риски. Представьте, что у вас есть еще одна переменная

int * v_ptr;

не важно, будет v_ptr глобальной, статической или локальной. Главное, что ее область видимости будет больше области видимости local_var_3. Что произойдет, если вы внутри цикла do while из примера выполните такое присвоение

v_ptr=&local_var_3;

а использовать переменную v_ptr будете после выхода из цикла? Вы будете обращаться по адресу уже не существующей переменной!

* Тип размещения register

Регистровые переменные изначально задумывались как способ ручного размещения переменных в регистрах процессора для ускорения выполнения критичных участков кода или для специфических случаев. В большинстве случаев этот модификатор просто игнорируется компилятором. Да, он мог бы быть полезным, например, для AVR, где есть 32 регистра процессора. И компилятор avr-gcc позволяет использовать регистровые переменные.

Однако, вы должны понимать, что таким образом вы вмешиваетесь во внутренние правила использования регистров компилятором, а значит, должны точно знать, что и зачем делаете. И как это работает. Использование регистровых переменных выходит за рамки "для начинающих".

Однако, хочу обратить ваше внимание, что регистровые переменные не имеют адреса.

* Тип размещения static

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

Так local_static_var из нашего примера будет существовать даже тогда, когда func не выполняется. И она будет сохранять свое значение между вызовами.

Эта переменная будет размещена во время компиляции, а свое начальное значение получит еще до передачи управления функции main. То есть, еще до начала выполнения основной программы. Именно по этой причине строка 7 не будет являться выполняемой при передаче управления func, о чем я говорил ранее.

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

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

* Тип размещения extern

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

Константы

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

Так константу можно вообще не размещать в памяти а заменять на литерал, непосредственно размещаемые в коде машинной команды данные. В нашем примере так можно поступить с обеими константами, и constant_var_1, и constant_var_2. Разумеется, в подобном случае получить адрес такой константы невозможно.

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

Наконец, константу можно разместить в ОЗУ, а ее начальное значение разместить в ПЗУ. При этом ограничение доступа на запись будет осуществляться компилятором. И компилятор же добавит необходимый код, что бы скопировать значение константы из ПЗУ в ОЗУ перед ее использованием. Однако, обратите внимание, что если так будет размещена локальная константа, что такое копирование будет выполняться при каждом входе в ее область видимости. А это может значительно замедлить выполнение программы. Разумеется, размещенная таким образом константа позволяет получить свой адрес. Причем указатель на нее будет совместим с указателем на обычную переменную.

Как именно константы размещаются в памяти зависит и от микроконтроллера, и от используемого компилятора.

Инициализируемые переменные

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

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

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

Не инициализируемые переменные

Не сомневаюсь, что большинство из вас помнят, что не инициализированные переменные использовать опасно, так из начальное значение может быть любым. Зачем же нам тогда такие переменные?

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

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

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

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

Более подробный взгляд на размещение элементов программы в памяти

Давайте попробуем немного систематизировать рассмотренные нами варианты размещения в памяти. И изучим это более подробно. Для этого нам нужно немного вспомнить, как память микроконтроллеров организована физически (и логически, в случае единого адресного пространства).

Упрощенная функциональная организации памяти абстрактного микроконтроллера. Иллюстрация моя
Упрощенная функциональная организации памяти абстрактного микроконтроллера. Иллюстрация моя
Упрощенная функциональная организации памяти абстрактного микроконтроллера. Иллюстрация моя

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

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

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

Размещение выполняемого кода

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

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

Размещать код функций начнет с определенного адреса, причем совершенно не обязательно с начала памяти программ. Почти наверняка будет пропущена область векторов прерываний. Точнее, в этой области будет располагаться автоматически сгенерированный специальный код.

Но давайте рассмотри специальные случаи размещения в памяти программ.

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

Как именно указывается адрес размещения функции зависит от компилятора. Это может быть директива pragma, специальный модификатор __attribute__, или какой то иной способ. В качестве примера приведу некоторые возможные варианты

#pragma page 3 // следующая функция размещается на странице 3

void func();

#pragma page - // возврат к стандартному размещению

-----------------------------------

void _page(3) func();

void __attribute__(at(0x1000)) func();

void func() @ 0x1000;

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

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

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

Пример размещения выполняемого кода в памяти программ. Иллюстрация моя
Пример размещения выполняемого кода в памяти программ. Иллюстрация моя
Пример размещения выполняемого кода в памяти программ. Иллюстрация моя

Здесь я показал все описанное выше. Серым цветом показаны области выполняемого кода функций. Для случая программы состоящей из одного исходного файла.

Размещение переменных

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

О каких непрерывных областях памяти данных идет речь? Во первых, это стек. Нам нужно место для автоматически размещаемых переменных. Размер стека трудно определить заранее. Просто не всегда возможно сразу сказать, какой вложенности будут области видимости и какова глубина вызова функций. Это может зависеть и от данных, которые программа обрабатывает. Поэтому размер стека обычно выбирают исходя из некоторых предположений о "стандартном случае".

А если точнее, то стек обычно просто размещают в конце памяти данных, так как он растет в сторону уменьшения адресов. При этом максимальный размер стека получается равным свободному от размещения статических переменных объему памяти данных.

При чем же тут "выбор размера"? Дело в том, что в один прекрасный (на самом деле, очень даже не прекрасный) момент свободного места для стека может оказаться не достаточно из-за того, что переменных стало слишком много. Или программа ведет себя не так, как ожидалось, из-за ошибки. А это приведет к искажению данных. По этой причине размер стека стараются контролировать, как то ограничивая его границы.

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

Переменные, которые нельзя инициализировать (помните, я говорил про это) можно поместить или в отдельную группу, или к инициализированным переменным. В отдельную группу можно выделить и константы.

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

И, естественно, нам нужна возможность как то указывать адрес размещения статических переменных в явном виде. Это требуется, например, для связи переменных и регистров оборудования. Адрес размещения в явном виде задается примерно так же, как адрес начала функций. Только могут использоваться немного иные ключевые слова. Например, вместо page может потребоваться писать bank. Поскольку все похоже, я не буду приводить примеры.

А теперь давайте посмотрим на очередную иллюстрацию

Пример размещения переменных и констант в памяти данных и памяти программ. Иллюстрация моя
Пример размещения переменных и констант в памяти данных и памяти программ. Иллюстрация моя
Пример размещения переменных и констант в памяти данных и памяти программ. Иллюстрация моя

Серым цветом показаны используемые нами для хранения переменных области памяти. Для констант я показал описанный ранее вариант, когда собственно рабочие копии констант размещаются в ОЗУ, а их начальные значения хранятся в ПЗУ. Для стека показано направление его роста и один из способов контроля его размера.

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

Логические области памяти

А если исходный файлов несколько и они компилируются независимо друг от друга? Или нужно разместить начиная с заданного адреса не одну переменную, а несколько? Или нужны переменные эквивалентные разделяемой памяти (shared memory) или аналог блоков COMMON Fortran?

Вот мы и подходим к новому для нас понятию программной секции (psect), сегмента (segment), области или зоны (area). Эти термины обозначают, в целом, одно и тоже. Вы можете встретить любой из них.

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

Итак, программная секция это логическая сущность использующаяся при распределении физической памяти.

Вот так просто, и кажется непонятным, мало что объясняющим. На самом деле мы уже сталкивались с программными секциями! Помните, я говорил об областях памяти выполняемого кода функций? И об областях памяти для размещения сгруппированных переменных. А ведь это и были программные секции, только в упрощенном виде.

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

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

Программные секции

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

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

А теперь давайте рассмотрим все эти атрибуты немного подробнее.

Имя секции

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

  • text, code, .text - секции для размещения выполняемого кода программы. При таком способе именования секций не делается различий между исходными файлами и функциями. При компиляции каждого исходного файла создаются одноименные секции, которые затем размещаются (компоновщиком) в соответствии с атрибутом режима соединения.
  • имя_файла_text, имя_функции_text - в целом, аналогично предыдущему варианту, но отдельные секции создаются для каждого исходного файла (в данном случае, это аналог модуля) или даже для каждой функции в исходном файле.
  • data - секция для переменных со статическим типом размещения. При этом не делается разницы между инициализированными и не инициализированными переменными.
  • idata - для инициализированных переменных со статическим типом размещения.
  • udata, bss - для не ициализированных переменных. Обычно эти секции очищаются при запуске программы специальными кодом, сгенерированным компилятором. Это то самое обнуление переменных.
  • stack - тут все ясно, это стек.
  • const - для констант. Обычно, размещаемых в ОЗУ.
  • abs - для переменных размещаемых по абсолютным адресам.

Секции с предопределенными адресами зачастую имеют и предопределенные атрибуты.

Иногда компилятор позволяет программисту создать свои секции. Это уже не для начинающих, но бывает полезно и продвинутым новичкам. Например, для размещения версии прошивки в специальной области памяти микроконтроллера. Для определяемых программистом секций нужно определять и полный список атрибутов. А зачастую и давать указания компоновщику, как поступать с такими секциями.

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

Тип содержимого секции

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

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

Тип позиционирования

С абсолютным позиционированием все понятно, задается адрес расположения секции в памяти соответствующего типа. А вот относительное позиционирование встречается редко. Я сказал о нем больше для общности картины, чем о практически используемом.

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

Пример размещения секции с относительным позиционированием. Иллюстрация моя
Пример размещения секции с относительным позиционированием. Иллюстрация моя
Пример размещения секции с относительным позиционированием. Иллюстрация моя

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

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

Адрес размещения

Ничего удивительного, что этот атрибут задает начальный адрес размещения секций с абсолютным позиционированием. Для секций с относительным позиционированием этот атрибут задает смещение (со знаком) относительно стандартного адреса размещения (после конца предыдущей секции).

Режим соединения одноименных секций

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

Приведу пример для двух исходных файлов.

Пример объединения программных секций с конкатенацией (con) и перекрытием (ovr). Иллюстрация моя
Пример объединения программных секций с конкатенацией (con) и перекрытием (ovr). Иллюстрация моя
Пример объединения программных секций с конкатенацией (con) и перекрытием (ovr). Иллюстрация моя

Хорошо видно, что секция b является аналогом разделяемой памяти или блоков COMMON (Fortran). Это позволяет различным модулям использовать одни и те же данные, причем с возможностью иной интерпретации их структуры без использования union. И без использования глобальных переменных.

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

Размер секции

В большинстве случаев вычисляется компилятором или компоновщиком. Но для создаваемых программистом секций его можно указать вручную. Это позволяет просто зарезервировать место, но не с помощью компилятора, а с помощью компоновщика. Например, так можно зарезервировать место для динамически распределяемой памяти (через malloc, realloc, free, как пример).

Типичное использование атрибута размера - для определения адреса начала размещения следующей секции.

Заключение

Вот собственно говоря и все. Все просто и логично. Я даже не буду приводить итоговую иллюстрацию с секциями вместо ранее изображенных областей. Можете просто взять два рисунка, размещение в памяти программ и размещение переменных и констант, и заменить названия областей на имена секций. И соедините оба рисунка в один. Получится прекрасная иллюстрация.

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

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

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

До новых встреч!