пятница, 30 марта 2012 г.

Что происходит в памяти компьютера?

Иногда мы бездумно объявляем переменные и не понимаем - как, где и зачем они хранятся в памяти компьютера.
Приведу пример из учебника информатики. Целое число хранится в 1-4 байтах (в зависимости от типа данных), при этом, если число со знаком, то первый старший бит отводится под знак. Итак, возьмем число 5 типа Integer (4 байта в среде FreePascal, так как этот тип здесь имеет значение longint). Получим следующий вид в битах:
00000000 00000000 00000000 00000101  - в двоичной системе счисления
   00       00       00      05      -  в 16-тиричной системе счисления
Если это -5, то сделаем операцию инвертирования
11111111 11111111 11111111 11111010  
и прибавим 1:
11111111 11111111 11111111 11111011  - в двоичной системе счисления
          FF                  FF               FF                  FB            - в 16-тиричной системе счисления

Попробуем получить это число с помощью программы на Паскале. Для этого будем использовать типизированные файлы:
var f: file of integer;
      i:integer;
begin
  assign(f,'int.dat');rewrite(f);
   i:=5;
   write(f,i);
   i:=-5;
   write(f,i);
   close(f);
end.
После выполнения программы посмотрим, что же хранится в файле. Для этого лучше всего использовать редактор в Far-manager. Выбираем просмотр файла (F3) и меняем кодировку (F4) на HEX (16-ричный вид). Получим следующую строчку:
0000000000: 05 00 00 00 FB FF FF FF и далее непонятные знаки и русские буквы "ыяяя".

Итак, чтоже мы видим? Первые нули до двоеточия показывают начало отсчета в памяти компьютера, т.е. адрес начинается с нуля. Далее идут числа в 16-тиричной записи по 2 цифры в каждом числе (2*4 бита для каждой цифры в 16-тиричной системе счисления=8 бит=1 байт), на каждое целое число в записи файла отводится по 4 байта, таким образом здесь записано 2 наших целых числа: 5 и -5 в том порядке, в каком они были записаны в файл. Но байты изменили свой порядок. Почему? Компьютеру (процессору)  легче читать с конца, это как в пирамидке: сначала большое кольцо надеваем на стержень, затем меньше и меньше, а забираем сверху - с младших байтов до старших. Вот и получается, что число в файле представлено - шиворот навыворот.

Теперь попробуем найти представление вещественных чисел в компьютере:
var f: file of real;
      i:real;
begin
  assign(f,'real.dat');rewrite(f);
  i:=1.0;write(f,i);
  i:=0.5;write(f,i);
  i:=0.25;write(f,i);
  i:=0.000125;write(f,i);
  i:=-84.25;write(f,i);

  i:=84.25;write(f,i);
  close(f);
end. 
Расберем эти числа на двоичное представление:
1,0=0,1*2^1
0,5=0,1*2^0  
0,25=0,01=0,1*2^(-2)=0,1*2^(-10)

-84,25=-1010100,01=-0,101010001*2^7=-0,101010001*2^111
0,000125=0,10000001...*2^(-13)=0,10000001...*2^(-1101) 

Для числа 0,000125 мантисса не вся выписана, только несколько цифр. Можно вывести все цифры, используя правило перевода дробных чисел в двоичный код. 
В памяти компьютера на действительное число типа Real отводится 8 байт (это в среде Free Pascal равно типу Double) и в файле будут следующие числа:
00 00 00 00 00 00 F0 3F -> 1,0
00 00 00 00 00 00 E0 3F -> 0,5
00 00 00 00 00 00 D0 3F -> 0,25
FC A9 F1 D2 4D 62 20 3F -> 0,000125
00 00 00 00 00 10 55 С0 -> -84,25


00 00 00 00 00 10 55 40 -> 84,25
Соответственно все числа в 16-тиричной системе нужно перевести в двоичную и записать в обратном порядке. Таким образом, в битах числа будут выглядеть так (в правильном порядке):
1,0:          00111111 11110000 00000000 00000000 00000000 00000000 00000000 00000000
0,5:          00111111 11100000 00000000 00000000 00000000 00000000 00000000 00000000
0,25:         00111111 11010000 00000000 00000000 00000000 00000000 00000000 00000000
0,000125:   00111111 00100000 01100010 01001101 11010010 11110001 10101001 11111100
-84,25:      11000000 01010101 00010000 00000000 00000000 00000000 00000000 00000000
84,25:       01000000 01010101 00010000 00000000 00000000 00000000 00000000 00000000
Разберем последнее число. Понятно, что первый старший бит отвечает за знак числа: 0 или 1. Далее идут цифры: 0101 00010000 - это мантисса числа (первые ненулевые числа после запятой 0,101010001 без первой единицы), 10000000101 - это порядок (степень двойки) с учетом машинного порядка 1000000000 и знака (-01, если степень со знаком и -10, если без знака), т.е. 1000000000+111-10=10000000101, 
для числа 0,000125 это 01111110010 - т.е. 1000000000-1101-01
для числа 0,25 это 0111111 1101 - т.е. 1000000000-10-01
для числа 0,5 это 0111111 1110 - т.е. 1000000000+0-10
для числа 1,0 это 0111111 1111- т.е. 1000000000+1-10



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

Интересно, а как хранятся данные в компьютере? Естественно - по порядку их объявления и тоже в виде стека. Давайте попробуем написать такую программу на С++:
#include <stdio.h>
#include <stdlib.h>
int main()
{
   int a,b,*c;
   a=5;
   c=&a; // указатель хранит адрес переменной a
   b=a*10;
   printf("%d %d\n",a, *c); /*вывод переменной a и значение в ячейке памяти по указателю *с */
   printf("%d %d\n",b, *(c-1));/* вывод переменной b и значение в ячейке памяти по указателю *с со сдвигом на 1 ячейку памяти "вверх", т.е. к значению переменной b.*/
   printf("%d\n",*(c+1)); /* в этом случае мы сдвинемся вниз и получим из памяти "мусор".*/
   return 0;
}
Из примера видно, что все динамические переменные, строки без заданной длины или массивы лучше описывать в конце списка описания переменных.

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

#include <stdio.h>
#include <stdlib.h>

int main()
{
int n, k, i; char *b, a;//важно именно в таком порядке записать переменные!
scanf("%c", &a);
b=&a; //запомним адрес памяти, с которого будем хранить строку символов.
k=1; i=0;
while(*(b-i)!='.')//Именно минус, чтобы идти вверх по пирамиде
{
 ++i;
 scanf("%c", *(b-i));
 if (*(b-i)==' ') k++;
}
 printf("\n %d",k);
 n=i;
 for (i=0;i<n;i++)
   printf("%c",*(b-i)); 
 return 0;
}

Так без объявления размера для динамического массива символов мы ввели все символы в память и вывели, зная только начало адреса ячейки памяти. Количество символов будет равно количеству сдвигов. Этот пример можно использовать для ввода строк неизвестной длинны. Если строки заканчиваются по клавише перехода на новую строку (Enter), то проверяем по коду 10: while(*(b-i)!=10){тело цикла}
Следующий код символа равен 13. Его тоже пропускаем 
++i; scanf("%c", *(b-i));
и можно дальше считывать следующую строчку символов.
Обошлись без дополнительных библиотек и функций.

Это пока все, что я узнала о памяти и готова поделится с Вами.

Комментариев нет:

Отправить комментарий