Date Редакция Категория comp Теги C / gdb

Адрес и размер: & и sizeof()

Переменные в С — это непрерывные блоки памяти. Каждый такой блок характеризуется двумя числами:

  1. адресом первого байта в блоке;
  2. размером блока в байтах.

Язык С позволяет узнать адрес переменной в памяти с помощью оператора &, а функция 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