Вопросы о том, как скомпилированные программы взаимодействуют с операционной системой - PullRequest
1 голос
/ 14 февраля 2011

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

(1) В Linux / Unix есть ли ограничения на размер пользовательских программ?Максимальный размер стека, который может иметь программа?Максимальный объем памяти в куче, которую может использовать пользовательская программа?

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

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

(4) Почему два файла (которые я собираюсь объединить в один исполняемый файл) не могут иметь глобальную переменную с одинаковым именем.Это поможет пролить некоторый свет на то, как выглядят объектные файлы.

Дополнение:

Я читаю стандарт ISO C99 с http://www.open -std.org / ОТК1 / SC22 / WG ... Docs / n1256.pdf .На странице 42 говорится:

6.2.2 Связи идентификаторов 1 Идентификатор, объявленный в разных областях или в одной и той же области более одного раза, может быть создан для ссылки на один и тот же объект или функцию с помощью процесса, называемого связыванием.Существует три вида связи: внешняя, внутренняя и отсутствует.

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

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

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

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

После прочтения этого выглядит так, чтоесли я объявлю переменную, как, скажем, int в 2 исходных файлах.тогда оба имеют внешнюю связь согласно правилу 5 и 4. и затем согласно правилу 2 оба должны ссылаться на один и тот же объект.Тогда почему компилятор создает проблему.Где в стандарте намекают, что мы не можем объявить так в 2 исходных файлах, и это должно привести к ошибке компиляции.

Спасибо.

Ответы [ 2 ]

3 голосов
/ 14 февраля 2011

В ответ на ваши вопросы -

  1. Большинство операционных систем используют виртуальную память, чтобы каждая программа считала, что ей принадлежит все адресное пространство.Это означает, что обычно пределом размера программы является объем физической памяти в системе, за вычетом небольшого объема памяти, который обычно зарезервирован для недопустимых (думаю, NULL) указателей и ядра.Максимальное ограничение памяти обычно зависит от платформы, но в 32-разрядных системах обычно ваши программы могут получить почти 4 ГБ памяти, а в 64-разрядной - гораздо больше.Конечно, вы также должны учитывать размер вашего диска, который ограничивает объем вашей виртуальной памяти.Теоретически вы могли бы написать программу настолько огромную, что не поместили бы ее в память, но если вы не используете встроенное устройство (где это действительно вызывает беспокойство), я сомневаюсь, что это когда-нибудь произойдет.1007 *

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

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

  3. Если у вас есть две глобальные переменные в разных файлах, которые связаны друг с другом, тогда оба объектных файла будут содержать символические ссылки, говорящие о том, что они должны ссылаться на переменную с этим именем, и на оба объектафайлы будут содержать определения, говорящие о том, что они предоставляют символ этого имени.Когда вы попытаетесь связать их вместе, компоновщик заметит, что одно и то же имя символа было определено в двух местах, и сообщит об ошибке, поскольку не уверен, какое из них следует использовать в качестве «экземпляра» этой глобальной переменной.Чтобы противодействовать этому, по крайней мере в C, вы можете пометить глобальные переменные static, чтобы дать им внутреннюю связь.Это делает символ не экспортируемым глобально, и поэтому сгенерированный объектный файл может либо разрешать ссылки внутри, либо искажать имя, чтобы он не конфликтовал с другими символами из других файлов.C ++ позволяет добиться этого, наряду с функцией анонимного пространства имен, для достижения того же эффекта.

Надеюсь, это поможет!Если кто-то обнаружит здесь ошибки или неясности, дайте мне знать, и я буду рад их исправить.

2 голосов
/ 14 февраля 2011
  1. Да, да и да.См. «Help ulimit» в bash или man getrlimit.

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

    При использовании «разделенного стека» (например, в Google Go, но работа над этим ведется, чтобыв gcc и других компиляторах для других языков) выделяется дополнительная память, которая находится не «в стеке», и указатель стека корректируется.Это динамически управляется при вызове функций и их возврате.

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

  4. Поскольку, по крайней мере для C и C ++, глобальные переменные могут быть определены только один раз во всей программе.Две единицы перевода (вы можете рассматривать TU как файлы .o) могут использовать глобальную переменную с одинаковым именем, но она может быть определена только один раз и должна быть объявлена ​​(с правильным типом) в других TU.Я не думаю, что здесь поможет понимание деталей объектных файлов, но понимание деталей того, что называется правилом единого определения (ODR) в C ++ или его эквивалентом на любом языке, который вы используете, вероятно, будет полезно.


Что касается редактирования, вы, вероятно, определили int в двух TU:

int this_is_a_definition;

Вы не можете сделать это.Вы должны объявить это в заголовке:

extern int this_is_a_declaration;

Затем включите этот заголовок, где требуется переменная, и определите переменную ровно в одном TU.Конечно, если вы не хотите использовать одну и ту же переменную в разных TU, то вам, вероятно, нужно «внутреннее» имя, например, которое вы получаете со статическим пространством имен или без имен:

static int local_to_this_TU;

namespace {
  int another_local_to_this_TU;
}
...