Можете ли вы когда-нибудь предположить, что типизация указателей безопасна?
Я слышал от многих людей, что вы не можете гарантировать, что типизация будет выполнена без потерь. Верно ли это только в том случае, если вы не знаете свой процессор, то есть вы не проверили количество байтов, используемых для ваших типов данных? Приведу пример:
Если вы выполните следующее:
typedef struct
{
int i;
char c;
float f;
double d;
} structure;
size_t voidPtrSz = sizeof(void *);
size_t charPtrSz = sizeof(char *);
size_t intPtrSz = sizeof(char *);
size_t floatPtrSz = sizeof(float *);
size_t doublePtrSz = sizeof(double *);
size_t structPtrSz = sizeof(structure *);
size_t funcPtrSz = sizeof(int (*)(float, char));
printf("%lun", voidPtrSz);
printf("%lun", charPtrSz);
printf("%lun", intPtrSz);
printf("%lun", floatPtrSz);
printf("%lun", doublePtrSz);
printf("%lun", structPtrSz);
printf("%lun", funcPtrSz);
... и выход следующий ...
4
4
4
4
4
4
4
Можно ли считать, что во всех случаях можно безопасно типизировать указатель определенного типа данных на указатель другого типа данных? Например, если выполняется следующим образом:
int foo(float, char)
{
}
void *bar(void)
{
return (void *)foo;
}
int (*pFunc)(float, char) = bar();
Можете ли вы с уверенностью предположить, что pFunc
имеет адрес foo
?3 ответа:
Можно ли считать, что во всех случаях можно безопасно типизировать указатель определенного типа данных на указатель другого типа данных?
Любой указатель данных можно безопасно привести к
char*
илиvoid*
. Любойchar*
илиvoid*
, созданный таким образом, может быть возвращен к своему исходному типу. Любое другое приведение указателя данных приводит к неопределенному поведению при выполнении косвенного обращения к указателю.Любой тип указателя функции может быть приведен к любому другому типу указателя функции, хотя вы не должны вызывать функция через неправильный тип. Приведение указателя функции к
void*
или любому другому типу указателя данных приводит к неопределенному поведению.Даже тогда ты не в безопасности. Когда стандарт C говорит, что конструкция имеет неопределенное поведение, авторы компилятора вольны обрабатывать конструкцию по своему усмотрению. В результате получается, что даже если вы думайте Вы знаете, что конструкция с UB будет обработана, потому что вы знаете целевой процессор, оптимизирующие компиляторы могут срезать углы и генерировать совсем другой код, чем вы ожидаете.Верно ли это только в том случае, если вы не знаете свой процессор, то есть вы не проверили количество байтов, используемых для ваших типов данных?
Что касается вашего конкретного примера кода, давайте обратимся к разделу 6.3.2.3 стандарта языка C99:
Обратите внимание, что указатель на функцию-это не то же самое, что указатель на объект. Единственное упоминание о указателе на функцию преобразования:Указатель на
void
может быть преобразован в или из указателя на любой неполный или объектный тип. Указатель на любой неполный или объектный тип может быть преобразован в указатель наvoid
и обратно; результат должен быть равен исходному указателю.Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно; результат должен быть равен исходному указателю. Если преобразованный указатель используется для вызова функции, тип которой не совместим с типом, на который указывает указатель, поведение не определено.
Таким образом, ваш пример кода вызывает неопределенное поведение.
Если мы избегаем преобразований функции-указателя, то следующий абзац объясняет: все:
Указатель на объект или неполный тип может быть преобразован в указатель на другой объект или неполный тип. Если результирующий указатель не выровнен правильно для типа, на который указывают, поведение не определено. В противном случае при обратном преобразовании результат сравнивается с исходным указателем.
Примечание: преобразование между типами указателей является отдельной проблемой от преобразования и затем разыменование (В общем случае, это допустимо только при преобразовании в
char *
и последующем разыменовании.)
@Oli Charlesworth дает вам отличный ответ.
Я надеюсь, что смогу пролить немного света на то, что такое указатель, чтобы вы могли лучше понять механику указателя:
Указатель-это адрес. Этот адрес является адресом первого байта ваших данных. Тип указателя определяет, сколько байтов, начиная с этого первого байта, являются частью данных и как эти байты кодируют данные.
Например, на gcc x86, если у вас есть
int * p
, значение, удерживаемое p, сообщает о начале адрес данных, и тип p (int *
) говорит, что по этому адресу он интерпретирует 4 байта (в малом конечном байтовом порядке) в дополнительном представлении числа со знаком два.Указатель
void *
является "универсальным указателем". Указатель по-прежнему содержит адрес, но тип указателя не указывает, какие данные вы там найдете, или даже сколько байт образуют данные, поэтому вы никогда не сможете получить доступ к данным через указательvoid *
, но, как было сказано ранее, вы можете безопасно преобразовать указатель в void и указатель на любой неполный или объектный тип.Указатель на функцию содержит адрес функции, а тип указателя указывает, как вызвать эту функцию (какие параметры и какого рода) и что функция возвращает.