Указатели и адресная арифметика
Память — это массив байтов
Для процессора память — длинный ряд пронумерованных байтов. Номер байта — его адрес. Каждая переменная занимает несколько подряд идущих байтов, и её адрес — адрес первого из них.
Указатель — переменная, значение которой — адрес другой переменной. Тип указателя (int *, double *) говорит, данные какого типа лежат по этому адресу.
Операторы & и *
int x = 10;
int *p = &x; /* & — взять адрес: p хранит адрес x */
printf("%d\n", *p); /* * — разыменовать: прочитать значение по адресу → 10 */
*p = 20; /* записать по адресу → x стало 20 */
printf("%p %p\n", (void *)p, (void *)&x); /* один и тот же адрес */
Аналогия
Переменная — квартира, адрес — номер квартиры, указатель — бумажка с номером. &x — узнать номер квартиры, *p — пойти по номеру с бумажки и заглянуть внутрь.
Звёздочка в объявлении int *p и звёздочка в выражении *p — разные вещи: первая говорит «p — указатель», вторая — «значение по адресу из p».
NULL
NULL — специальное значение «указатель никуда не указывает» (адрес 0). Функции возвращают его как признак ошибки (malloc), но не все: mmap при ошибке возвращает MAP_FAILED, то есть (void *)-1.
int *p = NULL;
if (p != NULL)
*p = 1; /* без проверки — Segmentation fault */
Адресная арифметика
Прибавление целого к указателю сдвигает его на столько элементов, а не байтов:
double arr[4] = {1.0, 2.0, 3.0, 4.0};
double *a = arr; /* имя массива — адрес первого элемента */
a + 1 /* адрес на 8 байт дальше (sizeof(double)) */
*(a + 2) /* 3.0 — то же, что a[2] */
| Выражение | Значение при a == 0x1000, тип double * |
|---|---|
a |
0x1000 |
a + 1 |
0x1008 |
a + i |
0x1000 + 8·i |
&a[69] |
0x1000 + 552 = 0x1228 |
Разность указателей одного массива — число элементов между ними.
Массивы и указатели
a[i] по определению означает *(a + i). Поэтому код sum += a[i] * b[i]; одинаково работает, объявлены ли a и b массивами или указателями. Но в памяти это разные вещи:
Задача 7
extern double *a; — указатель. Для каждого a[i] процессор читает: (1) значение указателя a из памяти; (2) элемент a[i]. Первое обращение — служебное, оно не связано с ячейкой массива. Если компилятор не держит a в регистре, это 2 лишних обращения на итерацию (для a и b).
Указатели на структуры
typedef struct { int state; unsigned counter; pid_t pid; pid_t ppid; } Mem;
Mem *mem = ...;
(*mem).counter = 0; /* разыменовать и взять поле */
mem->counter = 0; /* то же самое, короче */
void * и указатель на указатель
void * — «указатель на что угодно». Его нельзя разыменовать напрямую, но можно присвоить любому указателю. Так устроены malloc и mmap:
Mem *mem = mmap(NULL, sizeof(Mem), ...); /* mmap возвращает void * */
Указатель на указатель хранит адрес указателя: char **argv — массив строк аргументов командной строки.
int main(int argc, char **argv)
{
for (int i = 0; i < argc; i++)
printf("argv[%d] = %s\n", i, argv[i]);
}
Итог
- Указатель хранит адрес;
&берёт адрес,*разыменовывает,->обращается к полю структуры. p + iсдвигает наi * sizeof(*p)байт;a[i]≡*(a + i).- Указатель — отдельная переменная в памяти: чтобы прочитать
a[i]через глобальный указатель, нужно сначала прочитать сам указатель. - Разыменование
NULLили «висячего» указателя →SIGSEGV.
Практика решено 0 из 6
Что напечатает фрагмент?
int x = 10;
int *p = &x;
*p = 20;
printf("%d\n", x);
Указатель double *a содержит адрес 0x1000. Чему равен адрес a + 3? Ответ в hex, например 0x1003.
Что эквивалентно выражению a[i]?
Сколько обращений к памяти нужно процессору, чтобы прочитать a[i], если a — глобальная переменная-указатель
(double *a;, лежит в памяти), а i — в регистре? Считайте только обращения к данным.
Через указатель Mem *mem запишите в поле pid результат getpid(), а в поле ppid — getppid().
Используйте оператор ->.
Mem *mem = get_shared_memory();
mem->pid = getpid();
mem->ppid = getppid();
Какие из этих операций с указателем — ошибка?
Войдите, чтобы отмечать пройденные уроки.