Як комп’ютери запам'ятовують, де вони зберігають речі?


32

Коли комп'ютер зберігає змінну, коли програмі потрібно отримати значення змінної, як комп'ютер знає, де шукати в пам'яті значення цієї змінної?


17
Це не так; "комп'ютер" зовсім не звертає уваги. Треба жорстко кодувати всі адреси. (Що трохи спрощує, але не надто багато.)
Рафаель

1
@Raphael: Давайте узагальнимо це до "ми повинні жорстко кодувати базові адреси".
френель

Кожен раз, коли ви оголошуєте змінну, програма, відповідальна за запуск вашого коду, включає ім’я змінної з її адресою в хешбелі (він же простір імен). Я б запропонував прочитати книгу "Структура та реалізація комп'ютерних програм (SICP), щоб ознайомитись з такими маленькими подробицями"
Абхірат Махіал

Ваша вихідна програма використовує змінну. Компілятор або інтерпретатор вирішує, як це здійснити: він генерує інструкції для виконання комп’ютера та повинен переконатися, що ці втручання отримують значення з місць, в яких їх зберігалися попередні інструкції.
PJTraill

1
@AbhirathMahipal: змінній не потрібно мати адреси під час компіляції або навіть часу запуску; "Простір імен" - це мовна концепція, тоді як таблиця (хешована чи інша) - деталь реалізації; ім'я потрібно, щоб кивок зберігався в програмі під час його запуску.
PJTraill

Відповіді:


31

Я б запропонував вам заглянути в дивовижний світ Compiler Construction! Відповідь - це трохи складний процес.

Щоб спробувати дати вам інтуїцію, пам’ятайте, що імена змінних суто там, заради програміста. Комп'ютер врешті-решт перетворить усе на адреси.

Локальні змінні (як правило) зберігаються в стеку: тобто вони є частиною структури даних, яка представляє виклик функції. Ми можемо визначити повний перелік змінних, які функція використовуватиме (можливо), переглянувши цю функцію, тож компілятор може побачити, скільки змінних їй потрібно для цієї функції та скільки місця займає кожна змінна.

Існує трохи магії, що називається покажчиком стека, що є реєстром, який завжди зберігає адресу, де починається поточний стек.

Кожній змінній присвоюється "зміщення стека", де вона зберігається. Потім, коли програма повинна отримати доступ до змінної x, компілятор Замінює xз STACK_POINTER + x_offset, щоб отримати реальне фізичне місце , яке вона зберігається в пам'яті.

Зауважте, що саме тому ви отримуєте вказівник назад при використанні mallocабо newв C або C ++. Ви не можете визначити, де саме в пам’яті знаходиться виділене купою значення, тому вам доведеться тримати вказівник на нього. Цей покажчик буде на стеку, але він вказуватиме на купу.

Деталі оновлення стеків для викликів функцій та повернення є складними, тому я рекомендую Книгу Драконів або Книгу Тигра, якщо вас цікавить.


24

Коли комп'ютер зберігає змінну, коли програмі потрібно отримати значення змінної, як комп'ютер знає, де шукати в пам'яті значення цієї змінної?

Програма це говорить. Комп'ютери, як правило, не мають поняття "змінні" - це цілком мова мови високого рівня!

Ось програма C:

int main(void)
{
    int a = 1;
    return a + 3;
}

і ось код складання, який він компілює: (коментарі, починаючи з ;)

main:
    ; {
    pushq   %rbp
    movq    %rsp, %rbp

    ; int a = 1
    movl    $1, -4(%rbp)

    ; return a + 3
    movl    -4(%rbp), %eax
    addl    $3, %eax

    ; }
    popq    %rbp
    ret

Для "int a = 1;" процесор бачить інструкцію "зберігати значення 1 за адресою (значення регістру rbp, мінус 4)". Він знає, де зберігати значення 1, оскільки програма це говорить.

Аналогічно, наступна інструкція говорить "завантажувати значення адреси (значення регістру rbp, мінус 4) в регістр eax". Комп'ютеру не потрібно знати про такі речі, як змінні.


2
Щоб підключити це до відповіді jmite, %rspє покажчик стека CPU. %rbp- це регістр, який посилається на біт стека, який використовується поточною функцією. Використання двох регістрів спрощує налагодження.
MSalters

2

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

Адреса, записана в таблиці символів, може бути зміщеною з регістра (наприклад, вказівника стека), але це деталізація реалізації.


0

Точні методи залежать від того, про що конкретно ви говорите і наскільки глибоко ви хочете пройти. Наприклад, зберігання файлів на жорсткому диску відрізняється від зберігання чогось у пам'яті або зберігання чогось у базі даних. Хоча поняття схожі. І як це робити на рівні програмування - це інше пояснення, ніж те, як комп'ютер робить це на рівні вводу / виводу.

Більшість систем використовують якийсь механізм каталогу / індексу / реєстру, щоб дозволити комп'ютеру знаходити та отримувати доступ до даних. Цей індекс / каталог буде містити один або декілька ключів, а адреса, в якій фактично розміщені дані (будь то жорсткий диск, оперативна пам'ять, база даних тощо).

Приклад комп’ютерної програми

Комп'ютерна програма може отримати доступ до пам'яті різними способами. Зазвичай операційна система надає програмі адресний простір, і програма може робити все, що хоче, з цим адресним простором. Він може писати безпосередньо на будь-яку адресу в своєму просторі пам’яті, і він може відслідковувати те, як хоче. Іноді це може відрізнятися залежно від мови програмування та операційної системи, або навіть відповідно до бажаних методик програміста.

Як зазначалося в деяких інших відповідях, точне кодування або програмування, що використовується, відрізняється, але зазвичай за кадром воно використовує щось на зразок стека. У ньому є регістр, який зберігає місце пам'яті, з якого починається поточний стек, а потім метод знати, де в цьому стеку знаходиться функція або змінна.

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

Але, враховуючи, наскільки універсальне програмування, насправді немає однієї відповіді, оскільки програміст може вирішити записувати безпосередньо на будь-яку адресу у виділеному просторі в будь-який час (якщо припустити, що він використовує мову програмування, яка це дозволяє). Тоді він може зберігати його розташування в масиві або навіть просто жорсткий код його в програму (тобто змінна "альфа" завжди зберігається на початку стека або завжди зберігається в перших 32 бітах виділеної пам'яті).

Підсумок

Таким чином, за кадром повинен бути якийсь механізм, який повідомляє комп'ютеру, де зберігаються дані. Один з найпопулярніших способів - це якийсь індекс / каталог, який містить ключ (и) та адресу пам'яті. Це реалізується різними способами і, як правило, інкапсульоване від користувача (а іноді навіть інкапсульоване від програміста).

Довідка: Як комп’ютери запам'ятовують, де вони зберігають речі?


0

Це знає через шаблони та формати.

Програма / функція / комп’ютер насправді не знає, де що знаходиться. Просто очікує, що щось буде в певному місці. Давайте скористаємося прикладом.

class simpleClass{
    public:
        int varA=58;
        int varB=73;
        simpleClass* nextObject=NULL;
};

Наш новий клас 'simpleClass' містить 3 важливі змінні - два цілі числа, які можуть містити деякі дані, коли вони нам потрібні, та вказівник на інший об’єкт SimpleClass. Припустимо, що ми працюємо на 32-розрядній машині заради простоти. 'gcc' або інший компілятор 'C' створив би нам шаблон для роботи з розподілом деяких даних.

Прості типи

По-перше, коли використовується ключове слово для такого простого типу, як "int", компілятор робить примітку в розділі ".data" або ".bss" виконавчого файлу, так що при виконанні операційною системою дані доступні для програми. Ключове слово "int" виділить 4 байти (32 біта), а "long int" виділить 8 байт (64 біт).

Іноді в способі "по клітинці" змінна може надходити відразу після інструкції, яка повинна завантажувати її в пам'ять, щоб це виглядало так у псевдоскладанні:

...
clear register EAX
clear register EBX
load the immediate (next) value into EAX
5
copy the value in register EAX to register EBX
...

Це закінчилося б значенням "5" в EAX, а також EBX.

Під час виконання програми кожна інструкція виконується за винятком "5", оскільки безпосереднє завантаження посилається на неї і змушує процесор пропускати її.

Мінус цього методу полягає в тому, що він дійсно практичний лише для констант, оскільки зберігати масиви / буфери / рядки в середині коду було б недоцільно. Отже, загалом, більшість змінних зберігаються в заголовках програми.

Якщо вам потрібно було отримати доступ до однієї з цих динамічних змінних, тоді ви могли б трактувати безпосереднє значення так, ніби воно було вказівником:

...
clear register EAX
clear register EBX
load the immediate value into EAX
0x0AF2CE66 (Let's say this is the address of a cell containing '5')
load the value pointed to by EAX into EBX
...

Це закінчилося б значенням "0x0AF2CE66" в регістрі EAX та значенням "5" в регістрі EBX. Можна також додавати значення в регістри разом, тому за цим методом ми зможемо знайти елементи масиву або рядка.

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

Складні типи

Якщо ми зробимо два об'єкти цього класу:

simpleClass newObjA;
simpleClass newObjB;

тоді ми можемо призначити вказівник другому об'єкту на поле, доступне для нього в першому об’єкті:

newObjA.nextObject=&newObjB;

Тепер програма може очікувати пошуку адреси другого об'єкта в полі вказівника першого об’єкта. На пам'ять це виглядатиме приблизно так:

newObjA:    58
            73
            &newObjB
            ...
newObjB:    58
            73
            NULL

Тут слід зауважити дуже важливий факт, що "newObjA" та "newObjB" не мають імен при їх складанні. Вони просто місця, де ми очікуємо, що деякі дані будуть. Отже, якщо ми додаємо 2 клітинки до & newObjA, ми знаходимо комірку, яка діє як "nextObject". Отже, якщо ми знаємо адресу 'newObjA' і де клітина 'nextObject' відносна неї, то ми можемо знати адресу 'newObjB':

...
load the immediate value into EAX
&newObjA
add the immediate value to EAX
2
load the value in EAX into EBX

Це закінчилося б "2 + & newObjA" в "EAX" і "& newObjB" в "EBX".

Шаблони / формати

Коли компілятор компілює визначення класу, це справді компілюючи спосіб створення формату, спосіб запису до формату та спосіб читання з формату.

Наведений вище приклад - це шаблон для окремо пов'язаного списку з двома змінними 'int'. Такі конструкції дуже важливі для динамічного розподілу пам'яті, поряд з бінарними та n-ary деревами. Практичним застосуванням n-ary дерев є файлові системи, що складаються з каталогів, що вказують на файли, каталоги або інші екземпляри, розпізнані драйверами / операційною системою.

Для того, щоб отримати доступ до всіх елементів, подумайте про те, як дюймовий черв'як пробирається по конструкції вгору та вниз. Таким чином, програма / функція / комп’ютер нічого не знає, вона просто виконує вказівки щодо переміщення даних.


Слова "шаблон" та "формат", які тут використовуються, не містяться в жодному компіляторі чи підручнику компілятора, який я коли-небудь бачив, і, здається, немає жодної причини використовувати обидва слова для однієї і тієї ж неіснуючої речі. Змінні мають адреси та / або компенсації, це все, що вам потрібно знати.
користувач207421

Я використовую слова, оскільки вони є абстракціями для впорядкування даних, подібно до числа, файлів, масивів і змінних - це абстракції.
Містер Мінті Фреш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.