| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Для новичков > Работа программы |
| Автор: Hagrael 16.8.2011, 19:21 | ||
| Здравствуйте, форумчане. Да, я уже поднимал эту тему, но, по правде сказать, в тот раз на некоторые вопросы ответа я не получил и решил, что книга это исправит. Но нет, не исправила. Вот накопившиеся вопросы: 1) Как я уже усвоил из предыдущей темы, в Windows (а, может, во всех ОС?) есть виртуальная память. На протяжении всего времени exe-файл работает с ней. Но я слышал вопросы о том, что некоторые программисты в результате операции std::cout << &a в функции main видели у себя на экране разные цифры, а значит, переменные при разных запусках занимали разные позиции. А значит, что в коде не прописаны адреса переменных даже в их виртуальной памяти. Так каким же образом программа получает эти неслучайные адреса? 2) Как работает программа? Моя гипотеза: в ОЗУ заносится бинарник, а далее программа подгружает из него все функции и далее работает по бинарнику, пошагово выполняя инструкции, которые в нем записаны и переходя по нужным меткам. 3) Физическая память. Я прочитал, что из-за ее фрагментации если не будет подходящего по длине пустого фрагмента, то будет отказано в доступе к памяти. Это в корне противоречит тому, как я думал. Я считал, что если мы создаем в виртуальной памяти массив, то каждая из его ячеек может располагаться в абсолютно произвольном месте. Это не так? 4) Насколько я понимаю, объекты также могут быть автоматическими, как и другие переменные. Однако мой товарищ сказал мне, что объекты всегда хранятся в динамической памяти, даже когда мы этого явно не задаем. Кто из нас прав? 5) Когда использовать автоматические переменные, а когда - динамические? Из предыдущей темы я узнал, что автоматические переменные гораздо быстрее удаляются, а к динамическим гораздо быстрее доступ. Выходит, что если мы часто обращаемся к переменной, то ее надо объявить как динамическую, а если мы ее используем мало, то нам гораздо выгоднее, чтоб она быстро удалилась? И почему объекты надо всегда хранить в динамической памяти? 6) Я прочитал, что классы в памяти не хранятся, но как же тогда мы можем использовать их методы подобным образом: name::method() ? 7) Правда ли, что слова private, public и protected перед членом класса в коде никаких изменений не делают и служат только для логики? 8) Бывают статические, динамические и автоматические переменные. Бывают ли еще какие-то, которые отличаются от этих по сроку жизни? 9) Я слышал, что структура и класс - одинаковые вещи за исключением тех мелочей, что поля у структуры по умолчанию декларируются как public, а у класса - private. Так ли это? 10) Когда создается автоматическая переменная? В том месте, где программист ее объявил в коде или при входе в блок кода? 11) Блок кода ведь тоже помещается в стек? Т. е. когда программа доходит в функции до блока кода ({блок}), то она создает новую ячейку в стеке, которая характеризует этот блок? 12) Объектный код - это по большей части исполняемый код? А линкер, выходит, просто подставляет нужные адреса переменным, и все? Если так, то в теории один и тот же линкер может отлинковать объектный код, сгенерированый разными компиляторами! В wiki просто прочел вот это:
Уважаемые программисты! Простите за огромное количество вопросов! Буду очень благодарен тому, кто захочет помочь! |
| Автор: mes 16.8.2011, 19:51 |
| 1. http://liveworkspace.org/code/57b10cb6dc0917d3204dc62fb7d0fe43 |
| Автор: Hagrael 16.8.2011, 19:57 | ||||||
boostcoder, спасибо за ответ!
Но тогда вообще зачем использовать динамические переменные?
Действительно, я сейчас сам понял, что вопрос ошибочен.
ОК ОК, почитаю, чем они отличаются в других языках. Добавлено через 1 минуту и 15 секунд mes, т. е. на самом деле место в виртуальной памяти у переменной всегда одно и то же? Даже когда речь идет о динамической памяти? |
| Автор: boostcoder 16.8.2011, 20:05 |
динамические, я крайне редко использую. обычно обернутые в shared_ptr. mes, а что нужно понять из вашего примера? адрес переменной i одинаковый в двух вызовах. но функция не статическая. и переменная не статическая. поэтому мне кажется это просто случайность, или линкер очень грамотно понял ситуацию. или что? |
| Автор: asmdzen 16.8.2011, 20:21 |
так разве не так и должно быть, при вызове функции место для переменной выделяется в стеке, когда f() вызывается из g() в стеке присутствует уже и адрес возврата из g() в main(), вот только не понятно куда девались еще 12 байт, или это какие то push esp? |
| Автор: mes 16.8.2011, 20:22 |
ммм.. функция одна, стек один, если на стеке никого больше нет, то адрес автоматической переменной будет одним и тем же при каждом вызове.. а если есть то меняется, это и показывает пример.. |
| Автор: boostcoder 16.8.2011, 20:24 |
| mes, ну да.. понял. |
| Автор: asmdzen 16.8.2011, 20:30 | ||
но не тем же при перезапуске программы ) |
| Автор: Hagrael 16.8.2011, 20:31 |
| Так что, может одна и та же переменная в коде при различных обстоятельствах иметь разные адреса? |
| Автор: mes 16.8.2011, 20:42 | ||
да |
| Автор: rodnover 16.8.2011, 20:54 |
| 1, 2. Для загрузки файла в память используются первая часть файла exe (можно открыть в любом HEX редакторе и поковырять). На сайте wasm.ru, в принципе, есть много информации по структуре этого блока (PE-заголовок). В этом заголовке указаны базовый адрес программы, виртуальные адреса всех сегментов (кода, данных, стека) относительно базы и их размеры. На основе этих данных выполняется защита памяти (как, например, ошибки "память не может быть Read"). Да, забыл, в этом же заголовки прописываются все функции из внешних dll, подключенных статически. Во всей программе используются относительные адреса от базового адреса. Например, функция PrintMyStr() расположена на +0x100 байт от базового адреса. std::string my_str расположена +0x92 от базового адреса. 3. Управлением памятью осуществляют специальные менеджеры памяти, в задачи которых входит, выделение, освобождение страниц памяти, возможно, их дефрагментация и т.д... 4. Динамические объекты могут работать создаваться медленнее, в следствии каких-либо алгоритмов менеджера памяти, в то время, как на выделение объекта в стеке, фактически вызывают одну команду смещения указателя на верхушку стека. Но не думаю, что это замедление хоть сколько-то заметно. 5. Стек ограничен. В нем, помимо переменных хранится множество служебной информации (адреса возвратов из подпроцедур, блоки исключений). Наверно, всем встречалось сообщение об ошибке "Stack overflow" вызванное бесконечной рекурсией. Поэтому, в динамической памяти необходимо помещать крупные блоки информации. |
| Автор: mes 16.8.2011, 21:03 |
создаваться.. |
| Автор: rodnover 16.8.2011, 21:05 |
| Ой да, конечно. Я думаю, там намного больше описок и неточностей. ;) |
| Автор: Hagrael 16.8.2011, 21:06 | ||
Он поставляется вместе с ОС или это нечто еще более низкоуровневое? А как программа узнает, какой адрес стоит занять? Ей дает об этом знать менеджер памяти? Базовый адрес - это адрес в виртуальной памяти? Если все остальные адреса указаны относительно него, а сам базовый адрес указан в файле, то одна и та же переменная обязана каждый раз иметь тот же адрес (сам я понимаю, что это невозможно, но все идет к этому).
Т. е. объекты могут быть автоматическими, да? |
| Автор: asmdzen 16.8.2011, 21:13 |
в том то и дело что он не указан, система его выдает когда загружает бинарник в память. |
| Автор: rodnover 16.8.2011, 21:14 | ||
| С ОС по умолчанию, но знаю, что билдер и делфи свой менеджер памяти используют. Базовый адрес (получается виртуальный, обычно указывается 0x400000. С количеством нулей могу напутать) программы указывается в PE-заголовке. Какой там указан - с такого и будет грузится. То есть, базовый адрес - это просто значение, которое выбирается самой программой и от которой она начинает плясать.
Динамически созданные переменные могут распологаться в разных адресах кучи (int *c пример указателя на кучу). Что в вашем понимании "автоматические переменные"? |
| Автор: Hagrael 16.8.2011, 21:23 | ||||||||||
Автоматическая переменная - это переменная, которая располагается в стеке блока и удаляется при выходе из него. Это неправильное определение?
Ясно.
Кто из вас прав? Натолкнулся на 2 новых вопроса:
Надеюсь, понятно изъяснился. |
| Автор: rodnover 17.8.2011, 06:44 | ||||||
Я просто не встречался с таким определением.
Я вот что имел ввиду. Физический адрес, то будет разным, вот только есть ли простой способ получить его из программы? http://www.wasm.ru/article.php?article=vgw03 База образа:
10. Тут, вообще, много зависит от компилятора и параметров оптимизации. Например, при некоторых параметрах оптимизации одни и те же ячейки памяти будут использоваться для разных переменных. 11. Код распологается в сегменте кода (это отдельный сегмент), а в стек помещается адрес возврата из функции (попробуйте поизучать ассемблер). Вот первая ссылка из гугла http://www.insidepro.com/kk/144r.shtml |
| Автор: mes 17.8.2011, 08:17 | ||||
грубо: у объекта(переменной) есть время жизни.. на это время он (и только он) занимает определенные ячейки памяти.. http://liveworkspace.org/code/a0a9ebcea644a3e33dcb7012796df81b Добавлено @ 08:20 пример показывает, что в разные промежутки времени на одном адресе расположены разные объекты.. функция/блок кода знает смещение в стеке до нужной переменной, и поэтому ей не важно реальный адрес переменной.. |
| Автор: rodnover 17.8.2011, 08:22 |
| http://liveworkspace.org/code/e7691f4dd47cc6b098fa6e8b898da032 Вот я про такой пример имел ввиду. При некоторых опциях компилятора адрес будет одинаковым. P.S. Сайт отличный. Вообще в восторге. |
| Автор: mes 17.8.2011, 08:30 | ||||
вообще то в том примере он для всех тех переменных всегда будет одинаков Добавлено @ 08:32
кажется я понял, что тс непонятно и чего мы упустили.. Hagrael, почитайте про регистры процессора, именно через них он узнает какой кусок кода выполнять и где начало данных конкретного блока/функций.. |
| Автор: rodnover 17.8.2011, 08:36 | ||||
В примере из моего сообщения на сайте адреса неодинаковы. ;) Но при некоторых опциях будут одинаковы.
Вот заставили человека ассемблер изучать, но мысль верная и полезная. |
| Автор: Hagrael 17.8.2011, 08:39 | ||||||||||||
| Классная статья, rodnover! Правда описывает она мало, и после ее прочтения появилась уйма вопросов Думаю, вначале следует разобраться только со структурой самого exe-файла, не же ли сразу переходить к заголовку. Буду читать.
Т. е. в стандарте не сказано, когда переменная должна создаваться - при входе в блок кода или же в том месте, где она создается в коде?
Вот я и думаю его поучить. Но у меня сейчас не так много времени. Хотя действительно надо бы постараться. Я почитаю про стеки вызовов функций. Как я понял, автоматические переменные - это то же самое, что и локальные?
Так "Да" или "Нет"? Появился новый вопрос:
Спасибо за помощь! |
| Автор: asmdzen 17.8.2011, 08:40 | ||
а Вы перезапустите пример mes'а и посмотрите как меняется адрес переменной. т.е. насколько я помню в заголовке PE указывается адрес куда программа хотела бы загрузится, а потом система уже сама определяет может она ее туда загрузить или нет.
да, но когда эта память выделяется для него, ведь IMHO писать все переменные в начале функции или по мере необходимости уже в коде - дает примерно тот же результат, т.е. место в стеке для всех переменных выделяется при входе в функцию, разница только в том что конструкторы будут вызываться уже там где объект был объявлен. |
| Автор: Hagrael 17.8.2011, 08:48 | ||
| Многие говорят: "Размер переменной зависит от платформы. Точных данных нет" etc. А от чьей платформы? Платформы разработки программы или же платформы пользователя? Как я понимаю, второе. Тогда, выходит, что всеми размерами переменных управляет Windows? Добавлено через 2 минуты и 13 секунд
Ответ получен! |
| Автор: mes 17.8.2011, 08:55 | ||
sorry, этот пример не увидел.. и при каких же это опциях два объекта с пересекающимсе временем жизни могут занимать один и тот же адрес ?! |
| Автор: Hagrael 17.8.2011, 09:04 | ||
Хм. У меня тут в голове промелькнуло в голове: "А зачем вообще в файле указывается базовый адрес, если Windows его очень навряд ли даст? (а вообще, Windows на него внимание обращает?) И чем этот базовый адрес будет лучше того, что даст Windows?" |
| Автор: mes 17.8.2011, 09:11 | ||||
для стековых переменных память не выделяется вообще (она выделенна изначально для всего стека) .. Добавлено @ 09:13
затем , что если загрузчик хочет загрузить по другому адресу, он должен подправить связи.. |
| Автор: rodnover 17.8.2011, 09:13 | ||||||||||
В контексте получается - да, тоже самое.
Опять таки, в конексте - могут.
Windows, Linux, MacOS, тип процессора (16, 32, 64 битный) и так далее.
Если компилятор определяет, что переменная больше не используется, то он может начать в этой ячейке хранить другую переменную.
Сколько раз я проверял под отладчиком винда всегда выдавала мне тот адрес который указан в PE-заголовке. А вообще это просто точка отсчёта, для ориентации по памяти программы все смещения в программе (jmp, jz, jnz, call) указаны относительные. Некоторые защиты строятся на изменении этого адреса. 12. В принципе да, если у него будет одинаковый формат и одинаковые соглашения. |
| Автор: asmdzen 17.8.2011, 09:15 |
я имел ввиду смещается указатель на вершину стека, Вы меня поняли ) вообще то дает ) писали уже что есть оптимизация для этого, т.е. если программа загрузится куда хотела то будет работать "быстрей" |
| Автор: rodnover 17.8.2011, 09:18 | ||
+ еще протекторы - добавляют свой сегмент, кодируют (сжимают) программу и указывают свой адрес входа. А при запуске распаковывают программу "где она и была" и передают управление ей. |
| Автор: mes 17.8.2011, 09:30 | ||
ох как скользко.. с точки зрения асм кода, да, может, также может и не занимать памяти вообще.. но это все оптимизация конечного кода.. но.. хотелось бы взглянуть на компилятор, на котором средставами языка, можно получить один и тот же адрес для разных переменных одного блока.. |
| Автор: rodnover 17.8.2011, 09:31 |
| union никто не отменял. |
| Автор: mes 17.8.2011, 09:39 |
а не разных переменных одной специальной структуры.. |
| Автор: Hagrael 17.8.2011, 09:39 | ||||||||||||
Вопрос почти решен. Только вот этого я не понял:
Буду признателен, если объясните.
Ответ получен
И для каждой из ОС свой метод компиляции, так? |
| Автор: rodnover 17.8.2011, 09:48 | ||||
| Вот на вскидку вспомнил http://www.aspack.com/asprotect.html В целом, это инструментарий, который сжимает, шифрует код программы (типа sfx архива) и при каждом запуске воссоздает оригинал программы, который и работает. Вот еще: http://ru.wikipedia.org/wiki/ASProtect
Метод то один, только условности разные. Например, в 32битных системах размер указателя 4 байта, в 64 битных - 8 байт, вызов внешних процедур, использование стека, регистров при вызове внешних процедур. Это тоже всё ассемблером успешно познается.
Потому я смайл и поставил. Хотя, структура то для экономии памяти и была придумана. Вообще, не вижу причин, чтобы преднамеренно указывать 2м переменным один адрес. |
| Автор: Hagrael 17.8.2011, 10:04 | ||||
Фрагментация памяти? Насчет протектора: он извлекает программу из архива и кладет в какую-то директорию как .tmp-файл, после чего запускает?
А размеры задаются при компиляции или же при запуске программы сообщаются ей с помощью ОС? |
| Автор: rodnover 17.8.2011, 10:15 | ||||||
Да не. Смысл экономить на том чего много?
При компиляции задаются. В простом случае компилятор создается под конкретную платформу. Но, также есть кросскомпиляторы (которых всё больше), в параметрах которых можно указать платформу, формат выходного файла, на основании которого они будут компилировать. Пример
cpu-type указывает под какой процессор компилировать. |
| Автор: mes 17.8.2011, 10:26 |
ну да именно для экономии памяти |
| Автор: bsa 17.8.2011, 10:45 |
| Нет. Он выделяет память, распаковывает туда код программы и передает ему управление. Если интересно, можешь посмотреть исходники такого упаковщика - upx. |
| Автор: xvr 17.8.2011, 11:16 | ||
Компилятор может поместить 2 разные переменные из одного блока в одно и то же место в памяти, если у них не пересекаются времена жизни (live range analysis). Но как только вы возьмете у переменной адрес, ее область жизни немедленно расширится до области жизни переменной с этим самым адресом (как минимум), а после этого у оригинальных переменных их области жизни пересекуться, и компилятор разведет их по разным адресам в памяти. Так что поместить 2 разных переменных по одному и тому же адресу компилятор теоритически может, а вот увидеть это средствами языка увы нельзя. |
| Автор: mes 17.8.2011, 13:07 | ||||
и пока мы не взяли адрес (пусть косвено, посредством передачи объекта по ссылке) некорректно говорить о том, что объект располагается по какому либо определенному адресу... хм .. что то мне это вдруг странным образом напомнило одного знаменитого кота.. раньше как то внимание не заострялось на этом |
| Автор: rodnover 17.8.2011, 13:11 |
| Оптимизация Кота? |
| Автор: mes 17.8.2011, 13:25 |
| Кот Шрёдингера |
| Автор: boostcoder 17.8.2011, 13:34 | ||
|
| Автор: Hagrael 17.8.2011, 15:53 |
| Т. е. протектор загружается загрузчиком программ, проделывает нужные операции... А дальше что? Программа-то должна загрузиться загрузчиком программ, чтобы тот сделал все нужные выводы, сообщил о чем надо Windows etc. |
| Автор: rodnover 17.8.2011, 17:05 | ||||
| типа такого: оригинальная программа
Загрузчик делает обертку над программой, пакует, кодирует и получается такой псевдокод:
Грубо, конечно, но примерно так. |
| Автор: maydjin 17.8.2011, 20:53 | ||
Не совсем верно, а про оптимизацию совсем не верно. Базовый адрес записываеться компоновщиком на этапе сборки программы, и может быть изменен соответсвующими опциями оного. На самом деле это не совсем адрес - это смещение относительно начала виртуально адресного пространства, которое у каждого процесса своё. Не знаю точно как всё дело происходит под linux и другие платформы (хотя думаю существенных отличий нет), а под win это выглядит примерно так: 1. Загрузчик получает уведомление о запуске нового процесса 2. Загрузчик вызывает менеджер памяти, который выделяет под x32 2^32 под x64 2^64 байт виртуальной памяти процессу(за предоставление этой памяти отвечае страничный мененджер). Процессу по умолчанию доступна только половина адресного пространства, доступ к второй половине вызовет ошибку, т.к она доступна только ядру. 3. Загрузчик считывает pe заголовок и размещает в виртуальном адресном пространстве код и статические данные. 4. Ядро создает основной поток процесса выделяя память под стек относительно адреса базы, при том на сколько я помню начало стека будет как раз этим самым базовым адресом.(Тут не совсем уверен - нет под рукой отладчика если базовый адресс не адрес начала стека, то это адрес первой инструкции кода основного потока) 5. Управление передаеться программе Подробней почитать/спросить можно на вышеупомянутом сайте wasm.ru. Не совсем уверен за п4 но если посмотреть адреса локальных переменных в основном потоке то похоже на правду. Главный мессадж моего топика : Да программа всегда загружаеться по базовому адресу. Но этот адрес расположенн в виртуальном адресном пространстве, т.е физический адрес на 99,9999......% будет каждый раз новый(ну по крайней мере примерно первые <количество dword в вашей оперативной памяти> запуска под win32 Прошу прощения если есть какие нибудь неточности, но основную мысль надеюсь передал верно. |
| Автор: rodnover 18.8.2011, 06:23 |
| по пункту 4му - точно не верхушку стека указывает. Адреса и размеры сегментов ниже прописываются. |
| Автор: Hagrael 19.8.2011, 10:44 | ||
Спасибо за ответы, но сейчас я пытаюсь разобраться со стеками. И, признаться, не могу перевести некоторые фрагменты:
О каких распечатанных кружках-досках идет речь? о_О Добавлено через 54 секунды (да-да, это не информация о стеке, но изучая стеки, я вдруг обнаружил, что не понимаю термин ЦПУ). |
| Автор: rodnover 19.8.2011, 10:51 |
| Если я не ошибаюсь, то printed circuit boards переводится как печатная плата. |
| Автор: fish9370 19.8.2011, 11:59 |
| Hagrael, позволь дать совет, во-первых тебе нужно изучать линукс.. вижу твою тягу к системному программированию (или по крайней мере к изучению работы ОС) - линукс для этого подходит как нельзя лучше.. он открыт, под него довольно просто писать свои драйверы, есть и документация, и куча примеров.. во-вторых начни с книги "Разработка ядра Linux" (Роберт Лав).. ее можно купить или скачать в сети.. - очень простым языком описанно как работает ОС из каких подсистем она состоит и т.д.. там есть и сравнение винды и линукса, так что это тебе будет в любом случае интересно.. целенаправленно сядь и прочитай книжку.. |
| Автор: volatile 19.8.2011, 12:35 | ||
Приблизительный перевод: Центральный процессор (CPU), это часть компьютерной системы который обеспечивает выполнение инструкций компьютерной программы. Выполняет основные арифметические, логические операции и операции ввода/вывода. Процессор по сути - мозг компьютера. Термин существует в компютерной индустрии как минимум с начала 60-х годов. Хотя форма, схема и реализация ЦПУ значительно изменились по сравнению с первыми образцами, его основные предназанчение остается таким-же. На больших машинах процессоры занимают одну или несколько печатных плат. |
| Автор: Hagrael 19.8.2011, 15:23 |
| volatile, спасибо за дословный перевод. Не стоило, серьезно rodnover, спасибо за ответ. fish9370, м-м, не знал этой информации о Linux-e. Спасибо. Пост благодарностей |
| Автор: Hagrael 20.8.2011, 12:48 |
| Я приостанавливаю эту тему... снова. Вначале изучу микроконтроллеры, а потом буду читать про архитектуру компьютера. Всем спасибо! |
| Автор: bems 20.8.2011, 18:32 |
| для экзешника скорее всего именно енго и даст, потому что на момент его загрузки виртуальная память процесса практически пуста (экзешник загружается туда почти первым). А вот с длл да, тут как повезет |