Адрес и размер: &
и sizeof()
Переменные в С — это непрерывные блоки памяти. Каждый такой блок характеризуется двумя числами:
- адресом первого байта в блоке;
- размером блока в байтах.
Язык С позволяет узнать адрес переменной в памяти с помощью оператора &
, а функция sizeof()
вычисляет размер блока памяти занимаемого переменной.
С помощью GDB вы можете проделать следующее:
(gdb) print &i
$1 = (int *) 0xbffff1b8
(gdb) print sizeof(i)
$2 = 4
Это значит, что переменная i
размещается по адресу 0xbffff1b8
и занимает в памяти 4 байта.
Размер блока памяти, отведенного под переменную, зависит от ее типа:
(gdb) print sizeof(int)
$3 = 4
(gdb) print sizeof(double)
$4 = 8
То есть, по крайней мере, на моей машине, переменные типа int
занимают четыре байта, а типа double
— восемь байт.
Содержимое памяти по адресу: x
Для более детального анализа содержимого памяти в GDB есть команда x
, которая проверяет (eXamine, отчего и происходит ее название) память, начиная с определенного адреса. Формат команды определяет, сколько байт вы хотите проверить и в каком виде вывести их на экран:
x/nfu 0x<address>
n
: количество выводимых элементовf
: формат вывода (десятичный, шестнадцатиричный,...)u
: размер единицы данных (байт (b
), слово (w
), ...)
Например:
x/80c arr # посмотреть 80 символов, начиная с адреса `arr`
x/20dw arr # посмотреть 20 4-байтных целых чисел в десятичном формате
x/10xg arr # посмотреть 10 8-байтных целых (long long) в 16-ричном формате
Рассмотрим с помощью x
"внутренности" простейшей программы
#include <stdio.h>
int main()
{
int i = 1;
int j;
char c1 = 'a';
printf("value: %i, address: %p\n", i, &i);
printf("value: %i, address: %p\n", j, &j);
printf("value: %c, address: %p\n", c1, &c1);
}
Запустим ее в отладчике и остановимся на первой строке кода.
Temporary breakpoint 1, main () at /home/dima/work/tttest/xmemory.c:5
5 int i = 1;
(gdb) x/4xb &i
0xbffff1b8: 0xab 0x84 0x04 0x08
(gdb) x/xw &i # 1 можно не указывать
0xbffff1b8: 0x080484ab
(gdb) x/4db &i
0xbffff1b8: -85 -124 4 8
Мы вывели на экран 4 байта, начиная с адреса &i
, поскольку именно столько места занимает в памяти переменная i
. Заметим, что она еще не инициализирована.
Сделав еще один шаг, получим
(gdb) x/4xb &i
0xbffff1b8: 0x01 0x00 0x00 0x00
(gdb) x/xw &i
0xbffff1b8: 0x00000001
Обратите внимание, что на машинах Intel байты хранятся в порядке "от младшего к старшему", то есть справа налево, в отличии от более привычной нам записи слева направо.
(gdb) x/x &c1
0xbffff1b7: 0x00000100
(gdb) x/4xb &j
0xbffff1bc: 0x00 0x60 0xfb 0xb7
Переменная j
расположена в памяти через 4 байта от ранее объявленной i
и содержит "мусор". В то же время c1
, хотя и объявлена в тексте программы позже i
, но в памяти расположена раньше, занимая 1 байт, как и положено символу. Это интересно, поскольку можно было предположить другое поведение компилятора. Дело в том, что работа с отдельными байтами медленнее и "дороже" работы с целыми словами (то есть с 4-мя байтами для 32-разрядных машин и 8-ю байтами — для 64-разрядных). Поэтому, в целях повышения быстродействия, компилятор мог бы позволить дополнительный расход памяти и выделить под символ целое слово. Однако этого не произошло.
Более экзотический пример — выведем 4 слова (w), начиная с адреса функции main
:
(gdb) x/4xw &main
0x804841d <main>: 0x83e58955 0xec83f0e4 0x2444c720 0x00000118
Команда ptype
показывает тип C-выражения. Именно выражения, а не переменной:
(gdb) ptype j
type = int
(gdb) ptype j+c
type = int
Теперь с помощью GDB можно проиллюстрировать особенность реализации массивов в С, а именно, что имя массива является указателем на его первый элемент.
Добавим в нашу программу массив:
...
int a[] = {1, 2, 3};
}
Переменная a
имеет следующий тип:
(gdb) print a
$1 = {1, 2, 3}
(gdb) ptype a
type = int [3]
Теперь посмотрим как выглядит область памяти, занятая массивом:
(gdb) x/12xb &a
0xbffff1с0: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00
0xbffff1с8: 0x03 0x00 0x00 0x00
Тот же результат мы получим, если используем имя массива в качестве указателя на его (массива) первый элемент:
(gdb) x/12xb a
Мы можем применять к a
арифметические операции:
(gdb) print a + 1
$2 = (int *) 0xbffff1с4
Это означает, что a + 1 — это указатель на int, который содержит адрес 0xbffff1с4. Что же находится по этому адресу?
(gdb) x/4xb a + 1
0xbffff1с4: 0x02 0x00 0x00 0x00
А находится там элемент a[1]
массива a
. Другой способ убедиться в этом — это разыменовать указатель a + 1
(gdb) print *(a + 1)
$3 = 2
Итак, в С a[i]
эквивалентно *(a + i)
. Проверяем:
(gdb) print a[0]
$4 = 1
(gdb) print *(a + 0)
$5 = 1
(gdb) print a[1]
$6 = 2
(gdb) print *(a + 1)
$7 = 2
(gdb) print a[2]
$8 = 3
(gdb) print *(a + 2)
$9 = 3
Просмотр локальных переменных: info locals
Вывести список локальных автоматических переменных можно командой info locals
:
(gdb) info locals
i = 1
c = 97 'a'
j = -1208035856
a = {0, 134513883, -1208229888}
Как видно, на момент просмотра массив a
еще не инициализирован.
Комментарии
comments powered by Disqus