Как выполнить преобразование в другую структуру в стандартном C? - PullRequest
2 голосов
/ 28 апреля 2019

Я только что прочитал этот отрывок из PEP-3123

Стандарт C определяет, что доступ к объекту должен осуществляться только через указатель его типа, и что все другие доступы имеют неопределенное поведение, с несколькимиисключения.В частности, следующий код имеет неопределенное поведение ::

  struct FooObject{
    PyObject_HEAD
    int data;
  };

  PyObject *foo(struct FooObject*f){
   return (PyObject*)f;
  }

  int bar(){
   struct FooObject *f = malloc(sizeof(struct FooObject));
   struct PyObject *o = foo(f);
   f->ob_refcnt = 0;
   o->ob_refcnt = 1;
   return f->ob_refcnt;
  }

Проблема здесь в том, что к хранилищу обращаются так, как если бы он имел структуру struct PyObject, и как struct FooObject.

Исторически сложилось, что компиляторы не имели проблем с этим кодом.Однако современные компиляторы используют это предложение в качестве возможности оптимизации, обнаружив, что f->ob_refcnt и o->ob_refcnt не могут ссылаться на одну и ту же память и что, следовательно, функция должна возвращать 0 без необходимости извлекать значение ob_refcnt вообще вreturn return.

Мой C очень старомодный и очень ржавый, и, выполняя описанную выше операцию бесчисленное количество раз, я начал задаваться вопросом: в современном C дана структура A, если я хочу получить доступ к еемакет памяти со структурой структуры несвязанной структуры B, как это сделать?Из приведенного выше утверждения кажется, что это неопределенное поведение сейчас.

1 Ответ

3 голосов
/ 28 апреля 2019

в современном C, учитывая структуру A, если я хочу получить доступ к ее разметке памяти с структурой макета несвязанной структуры B, как это сделать?

Один не делает это в соответствующей программе.

Из приведенного выше утверждения кажется, что это неопределенное поведение.

Такая операция имеет неопределенное поведение в каждой версии стандарта C. Это не изменилось. Что изменилось, так это свободы, которые некоторые компиляторы могут принять во внимание из-за неопределенности поведения. Лучшая альтернатива - структурировать ваш код так, чтобы вы не проявляли такого поведения в первую очередь.

Предположим, что ваш код демонстрирует такое неопределенное поведение, и вы не хотите его изменять, вы можете рассмотреть объявление типа объединения, содержащего элементы обоих задействованных структурных типов:

union dummy {
    struct FooObject foo;
    struct PyObject  p;
};

В рамках такого объединения компилятор и компилятор , вероятно, сочтут небезопасным предполагать, что указатели на эти типы не дублируют друг друга.

Если вас беспокоит только конкретный компилятор, то вполне возможно, что вы можете вместо этого полагаться на параметр командной строки. Например, в GCC флаг -fno-strict-aliasing будет глобально отключать оптимизации, которые зависят от предположений о том, что указатели на разные типы не дублируют друг друга.

Но я подчеркиваю, что обе эти альтернативы - обходные пути, а не добросовестные решения.


Добавление:

Однако обратите внимание, что если первый член, объявленный макросом PyObject_HEAD, имеет тип struct PyObject, то ваш FooObject / PyObject не соответствует шаблону, о котором вы спрашивали: введите struct FooObject и struct PyObject тогда тесно связаны, поскольку FooObject имеет PyObject в качестве первого члена.

Если это так, то стандарт гарантирует, что если fp является указателем на FooObject, то (PyObject *) fp является действительным указателем на PyObject, который является его первым членом. Доступ к первому члену структуры через указатель на его тип, полученный описанным способом, имеет совершенно определенное поведение. Более того, если определение типа struct FooObject находится в области видимости, и оно объявляет struct PyObject в качестве своего первого члена, тогда это служит лучше и надежнее, чем объединение, подобное описанному выше.

Ничто из этого не относится к случаю структур, чьи ведущие элементы просто имеют одинаковые типы, в том же порядке, однако, использовал в качестве случая, полученного с использованием PyObject_HEAD. Весь смысл PEP-3123 состоял в том, чтобы перейти от этого случая к случаю, когда PyObject_HEAD объявляет (только) сам PyObject, как только что обсуждалось, а не отдельные члены одного.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...