- •7. Указатель на переменную и константу.
- •1. Присваивание указателю адреса существующего объекта:
- •10. Задание, инициализация, вывод переменных и их указателей.
- •11. Ограничения на использование основных
- •12. Указатель константа.
- •13. Константы и ссылки/указатели
- •5 4 2 1 3 // Порядок интерпретации описания
- •16. Многоуровневая адресация
- •17. Операции над указателями.
- •18. Пустой указатель (null-указатель).
- •14. Присваивание указателей различного типа.
- •23. Роль операции sizeof в управлении памятью
- •25. Работа с областью памяти переменного формата
23. Роль операции sizeof в управлении памятью
Операция sizeof вызывает подстановку транслятором соответствующего значения размерности указанного в ней типа данных в байтах. С этой точки зре-ния она является универсальным измерителем, который должен использо-
ватьсядля корректного размещения данных различных типов в памяти.
Сказанное проиллюстрируем простым примером размещения переменных типа double в массиве типа char:
double *d;
#define N 40
char A[N];
for (i=0, d=A; i < N / sizeof(double); i++)
d[i] = (double)i;
Заметим, что использование операции sizeof позво-
ляет сделать программу переносимой, то есть нечувствительной
к разрядности представления данных в различных трансляторах.
24. Указатель типа void*.
Наличие указателя определенного типа предполагает известную организацию памяти, на которую он ссылается. Но в некоторых случаях фрагмент программы "не должен знать" или просто не имеет достаточной информации о структуре данных в этой области. Тогда указатель должен пониматься как адрес памяти как таковой, с неопределенной организацией и неизвестной размерностью указуемой переменной. Такой указатель можно присваивать, передавать в качестве параметра и результата функции, но операции косвенного обращения и адресной арифметики с ним недопустимы.
Именно такими свойствами обладает указатель типа void* - указатель на пустой тип void. Наличие его в данном месте программы говорит о том, что она не имеет достаточных
оснований для работы с адресуемой областью памяти. Наиболее часто тип void* является формальным параметром или результатом функции. Приведем несколько примеров:
extern void *malloc(int);
int *p;
p = malloc(sizeof(int)*20);
...
p[i] = i;
Функция malloc возвращает адрес зарезервированной области динамической памяти в виде указателя void*. Это означает, что функцией выделяется память как таковая, безотноситель-но к размещаемым в ней переменным. Вызывающая функция неявно преобразует тип указателя void* в требуемый тип int* для работы с этой областью как с массивом целых переменных.
extern int fread(void *, int, int, FILE *);
int A[20];
...
fread(A, sizeof(int), 20, fd);
Функция fread выполняет чтение из двоичного файла n записей длиной по m байтов, при этом структура записи для функции неизвестна. Поэтому начальный адрес области памяти передается формальным параметром типа void*. При подстановке фактического параметра A типа int* производится неявное преобразование его к типу void*.
Как видно из примеров, преобразование типа указателя void* к любому другому типу указателя соответствует "смене точки зрения" программы на адресуемую память от "данные вообще" к "конкретные данные" и наоборот.
Операция приведения типа чаще всего используется для преобразования указателей. Например, стандартная функция захвата динамической памяти malloc возвращает указатель общего типа void* (см. раздел 3.7.3). Значение указателя обобщенного типа нельзя присвоить указателю на конкретный тип (язык C++ запрещает такие присвоения, Си-компиляторы иногда разрешают преобразования указателей по умолчанию, выдавая предупреждения, - но в любом случае это дурной стиль!). Для преобразования указателей разного типа нужно использовать операцию приведения типа в явном виде. В следующем примере в динамической памяти захватывается участок размером в 400 байт, его адрес присваивается указателю на массив из 100 целых чисел:
int *a; // Описываем указатель на массив типа int
. . .
// Захватываем участок памяти размером в 400 байт
// (поскольку sizeof(int) == 4), приводим указатель
// на него от типа void* к типу int* и присваиваем
// приведенное значение указателю a:
a = (int*) malloc(100 * sizeof(int));
Отметим, что допустимо неявное преобразование любого указателя к указателю обобщенного типа void*. Обратное, как указано выше, считается грубой ошибкой в C++ и дурным стилем (возможно, сопровождаемым предупреждением компилятора) в Си:
int *a; // Указатель на целое число
void *p; // Указатель обобщенного типа
. . .
a = p; // Ошибка! В C++ запрещено неявное
// приведение типа от void* к int*
a = (int*) p; // Корректно: явное приведение типа
p = a; // Корректно: любой указатель можно
// неявно привести к обобщенному