Такой код может иметь много преимуществ, но, к сожалению, стандарт C не был написан для его облегчения. Исторически компиляторы предлагали эффективные поведенческие гарантии, выходившие за рамки требований Стандарта, которые позволяли писать такой код более четко, чем это возможно в Стандарте С, но в последнее время компиляторы начали отзывать такие гарантии во имя оптимизации.
Наиболее примечательно, что многие компиляторы C исторически гарантировали (путем разработки, если не документации), что, если два типа структуры содержат одинаковую начальную последовательность, указатель на любой тип может использоваться для доступа к членам этой общей последовательности, даже если типы не связаны, и далее, что в целях установления общей начальной последовательности все указатели на структуры эквивалентны. Код, который использует такое поведение, может быть намного чище и более безопасным по типу, чем код, который этого не делает, но, к сожалению, даже несмотря на то, что Стандарт требует, чтобы структуры, разделяющие общую начальную последовательность, были изложены одинаково, он запрещает коду фактически использовать указатель одного типа для доступа к начальной последовательности другого.
Следовательно, если вы хотите написать объектно-ориентированный код на C, вы должны решить (и должны принять это решение на раннем этапе) либо перепрыгнуть через множество обручей, чтобы соблюдать правила типа указателя C, и быть готовым к современные компиляторы генерируют бессмысленный код, если он проскальзывает, даже если более старые компиляторы сгенерировали бы код, который работает как задумано, или иначе задокументировали бы требование, что код будет использоваться только с компиляторами, которые настроены для поддержки поведения указателя старого стиля (например, используя "-fno-строгий псевдоним") Некоторые люди считают "-fno-строгий псевдоним" злом, но я хотел бы предположить, что более полезно думать о "-fno-строгом псевдониме" C как о языке, который предлагает большую семантическую силу для некоторых целей, чем «стандартный» C,но за счет оптимизаций, которые могут быть важны для некоторых других целей.
Например, на традиционных компиляторах исторические компиляторы интерпретируют следующий код:
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
выполняя следующие шаги по порядку: увеличивать первый член *p
, дополнять младший бит первого члена *t
, затем уменьшать первый член *p
и дополнять младший бит первого члена *t
. Современные компиляторы переставлять последовательность операций в моде , какой код , который будет более эффективным , если p
и t
идентификации различных объектов, но изменить поведение , если они этого не делают.
Этот пример, конечно, намеренно придуман, и на практике код, который использует указатель одного типа для доступа к элементам, являющимся частью общей начальной последовательности другого типа, обычно будет работать, но, к сожалению, так как нет способа узнать, когда такой код может потерпеть неудачу его невозможно безопасно использовать, за исключением отключения анализа псевдонимов на основе типов.
Несколько менее надуманный пример мог бы возникнуть, если бы кто-то захотел написать функцию для выполнения чего-то вроде замены двух указателей на произвольные типы. В подавляющем большинстве компиляторов "1990-х C" это можно сделать с помощью:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
В Стандарте C, однако, нужно использовать:
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
Если *p2
он хранится в выделенном хранилище, а временный указатель не хранится в выделенном хранилище, эффективным типом *p2
станет тип временного указателя и код, который пытается использовать в *p2
качестве любого типа, который не соответствует временному указателю. Тип вызовет неопределенное поведение. Крайне маловероятно, что компилятор заметит такую вещь, но поскольку современная философия компилятора требует, чтобы программисты избегали Undefined Behavior любой ценой, я не могу придумать никаких других безопасных способов написания вышеуказанного кода без использования выделенного хранилища ,