﻿# Урок 15\. Паттерн 7\. Упаковка указателей

Большое количество ошибок при миграции на 64\-битные системы связано с изменением соотношения между размером указателя и размером обычных целых\. В среде с моделью данных [ILP32](https://pvs-studio.ru/ru/blog/terms/0018/) обычные целые и указатели имеют одинаковый размер\. К сожалению, 32\-битный код повсеместно опирается на это предположение\. Указатели часто приводятся к int, unsigned, long, DWORD и другим неподходящим типам\.

Следует четко помнить, что для целочисленного представления указателей следует использовать только [memsize](https://pvs-studio.ru/ru/blog/terms/0030/) типы\. Предпочтение, на наш взгляд, следует отдавать типу [uintptr\_t](https://pvs-studio.ru/ru/blog/terms/0050/), так как он лучше выражает намерения и делает код более переносимым, предохраняя его от изменений в будущем\.

Рассмотрим два небольших примера\.

```cpp
1) char *p;
   p = (char *) ((int)p & PAGEOFFSET);
2) DWORD tmp = (DWORD)malloc(ArraySize); 
   ...
   int *ptr = (int *)tmp;
```

Оба примера не учитывают, что размер указателя может отличаться от 32 бит\. Используется явное приведение типа, отбрасывающее старшие биты в указателе, что является явной ошибкой на 64\-битной системе\. Исправленные варианты, использующие для упаковки указателей целочисленные memsize типы \([intptr\_t](https://pvs-studio.ru/ru/blog/terms/0023/) и DWORD\_PTR\), приведены ниже:

```cpp
1) char *p;
   p = (char *) ((intptr_t)p & PAGEOFFSET);
2) DWORD_PTR tmp = (DWORD_PTR)malloc(ArraySize); 
   ...
   int *ptr = (int *)tmp;
```

Опасность двух рассмотренных примеров в том, что сбой в программе может быть обнаружен спустя очень большой промежуток времени\. Программа может совершенно корректно работать с небольшим объемом данных на 64\-битной системе, пока обрабатываемые адреса находятся в пространстве первых четырех гигабайт памяти\. А затем, при запуске программы на больших производственных задачах, произойдет выделение памяти за пределами этой области\. Рассмотренный в примерах код, обрабатывая указатель на объект вне данной области, приведет к неопределенному поведению программы\.

Следующий приведенный код не будет таиться и проявит себя при первом выполнении:

```cpp
void GetBufferAddr(void **retPtr) {
  ...
  // Access violation on 64-bit system
  *retPtr = p;
}
unsigned bufAddress;
GetBufferAddr((void **)&bufAddress);
```

Исправление также заключается в выборе типа, способного вмещать в себя указатель\.

```cpp
size_t bufAddress;
GetBufferAddr((void **)&bufAddress); //OK
```

Бывают ситуации, когда упаковка указателя в 32\-битный тип просто необходима\. В основном такие ситуации возникают при необходимости работы со старыми API функциями\. Для таких случаев следует прибегнуть к специальным функциям, таким как LongToIntPtr, PtrToUlong и так далее\.

Резюмируя, хочется заметить, что плохим стилем будет упаковка указателя в типы, всегда равные 64 битам\. Показанный ниже код вновь придется исправлять с приходом 128\-битных систем:

```cpp
PVOID p;
// Bad style. The 128-bit time will come.
__int64 n = __int64(p);
p = PVOID(n);
```

Поговаривают, что уже сегодня разработчики Microsoft Research работают над проектом по обеспечению совместимости ядер Windows 8 и Windows 9 с 128\-битной архитектурой\. Так что пишите хороший код сразу\.

## Диагностика

Упаковка указателя в 32\-битные типы диагностируется инструментом [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) посредством диагностических сообщений [V114](https://pvs-studio.ru/ru/docs/warnings/v114/) и [V202](https://pvs-studio.ru/ru/docs/warnings/v202/)\.

Следует заметить, что простые ошибки приведения указателей к 32\-битным типам хорошо диагностируются компилятором Visual C\+\+\. Например, компилятор предупредит об ошибке в коде, рассмотренном выше:

```cpp
char *p;
p = (char *) ((int)p & PAGEOFFSET);
```

Visual C\+\+ выдаст предупреждение "warning C4311: 'type cast' : pointer truncation from 'char \*' to 'int'"\. Однако, пример с GetBufferAddr уже не вызовет у Visual C\+\+ подозрений\. Таким образом, PVS\-Studio позволяет быть более уверенным в отсутствии таких ошибок, чем диагностика Visual C\+\+\.

Рассмотрим еще один важный момент, связанный с использованием анализатора PVS\-Studio\. Хотя некоторые из рассматриваемых в уроках ошибок могут быть обнаружены с использованием предупреждений Visual C\+\+, это не всегда получается осуществить на практике\. В больших и старых проектах, содержащих вдобавок большие сторонние библиотеки, часто отключены многие предупреждения, что снижает вероятность обнаружения 64\-битных ошибок\. Включать эти предупреждения бывает крайне нежелательно\. Предупреждений будет много, но только часть из них может помочь выявить ошибки 64\-битности\. Остальные предупреждения не будут относиться к делу, и скорее будут свидетельствовать о неаккуратности кода, чем о потенциальных 64\-битных проблемах\.

В описанной ситуации PVS\-Studio окажет неоценимую услугу\. Он выделит в коде только те потенциальные ошибки, которые относятся к вопросам переноса проекта на 64\-битную архитектуру\.

_Авторы курса: Андрей Карпов \([karpov@viva64\.com](mailto:karpov@viva64.com)\), Евгений Рыжков \([evg@viva64\.com](mailto:evg@viva64.com)\)\._

_Правообладателем курса "Уроки разработки 64\-битных приложений на языке Си/Си\+\+" является ООО "Системы программной верификации"\. Компания занимается разработкой программного обеспечения в области анализа исходного кода программ\. [Сайт компании](https://pvs-studio.ru/ru/pvs-studio/)\._