Нет общего требования, чтобы реализации использовали одни и те же представления для разных типов указателей. На платформе, которая будет использовать другое представление, например, для int*
и char*
, не было бы никакого способа поддерживать один тип указателя void*
, который мог бы действовать как на int*
, так и char*
взаимозаменяемо. Хотя реализация, которая может обрабатывать указатели взаимозаменяемо, облегчила бы низкоуровневое программирование на платформах, которые используют совместимые представления, такая возможность не будет поддерживаться на всех платформах. Следовательно, у авторов Стандарта не было никаких оснований требовать поддержки такой функции, а не рассматривать ее как вопрос качества реализации.
Из того, что я могу сказать, качественные компиляторы, такие как icc, которые подходят для низкоуровневого программирования и которые предназначены для платформ, где все указатели имеют одинаковое представление, не будут иметь проблем с такими конструкциями, как:
void resizeOrFail(void **p, size_t newsize)
{
void *newAddr = realloc(*p, newsize);
if (!newAddr) fatal_error("Failure to resize");
*p = newAddr;
}
anyType *thing;
... code chunk #1 that uses thing
resizeOrFail((void**)&thing, someDesiredSize);
... code chunk #2 that uses thing
Обратите внимание, что в этом примере оба действия по получению адреса объекта, и все используют результирующий указатель, явно происходят между двумя фрагментами кода, которые используют thing
. Таким образом, фактического псевдонима не существует, и любой компилятор, который не является преднамеренно слепым, не будет иметь проблем с распознаванием того, что акт передачи адреса thing
в reallocorFail
может привести к изменению thing
.
С другой стороны, если использование было что-то вроде:
void **myptr;
anyType *thing;
myptr = &thing;
... code chunk #1 that uses thing
*myptr = realloc(*myptr, newSize);
... code chunk #2 that uses thing
тогда даже качественные компиляторы могут не осознавать, что thing
может быть затронут между двумя фрагментами кода, которые его используют, поскольку между этими двумя фрагментами нет ссылок на что-либо типа anyType*
. На таких компиляторах необходимо написать код примерно так:
myptr = &thing;
... code chunk #1 that uses thing
*(void *volatile*)myptr = realloc(*myptr, newSize);
... code chunk #2 that uses thing
, чтобы сообщить компилятору, что операция на *mtptr
делает что-то "странное". Качественные компиляторы, предназначенные для низкоуровневого программирования, будут расценивать это как признак того, что им следует избегать кэширования значения thing
во время такой операции, но даже квалификатора volatile
будет недостаточно для реализаций, таких как gcc и clang, в оптимизации режимы, предназначенные только для целей, не связанных с низкоуровневым программированием.
Если функция, подобная reallocOrFail
, должна работать с режимами компилятора, которые на самом деле не подходят для низкоуровневого программирования, ее можно записать так:
void resizeOrFail(void **p, size_t newsize)
{
void *newAddr;
memcpy(&newAddr, p, sizeof newAddr);
newAddr = realloc(newAddr, newsize);
if (!newAddr) fatal_error("Failure to resize");
memcpy(p, &newAddr, sizeof newAddr);
}
Это, однако, потребовало бы, чтобы компиляторы допускали возможность того, что resizeOrFail
может изменить значение произвольного объекта любого типа - не просто указателей данных - и, таким образом, излишне ухудшить то, что должно быть полезным для оптимизации. Хуже того, если рассматриваемый указатель хранится в куче (и не относится к типу void*
), соответствующие компиляторы, которые не подходят для низкоуровневого программирования, все равно могут предположить, что второй memcpy
не может повлиять на это.
Ключевой частью низкоуровневого программирования является обеспечение выбора подходящих реализаций и режимов и знание того, когда им может понадобиться квалификатор volatile
, чтобы помочь им. Некоторые поставщики компиляторов могут утверждать, что любой код, который требует, чтобы компиляторы подходили для его целей, был «сломан», но попытка умиротворить таких поставщиков приведет к тому, что код будет менее эффективен, чем можно было бы создать, используя качественный компилятор, подходящий для своих целей.