Интерфейсы в C - PullRequest
       60

Интерфейсы в C

0 голосов
/ 07 февраля 2019

Я проектирую приложение и столкнулся с проблемой реализации.У меня есть следующее определение структуры:

app.h:

struct application_t{
    void (*run_application)(struct application_t*);
    void (*stop_application)(struct application_t*);
}

struct application_t* create();

Проблема возникла, когда я попытался «реализовать» это application_t.Я склонен определять другую структуру:

app.c:

struct tcp_application_impl_t{
    void (*run_application)(struct application_t*);
    void (*stop_application)(struct application_t*);
    int client_fd;
    int socket_fd;
}

struct application_t* create(){
     struct tcp_application_impl_t * app_ptr = malloc(sizeof(struct tcp_application_impl_t));
     //do init
     return (struct application_t*) app_ptr;
}

Так что, если я использую это следующим образом:

#include "app.h"

int main(){
    struct application_t *app_ptr = create();
    (app_ptr -> run_application)(app_ptr);    //Is this behavior well-defined?
    (app_ptr -> stop_application)(app_ptr);   //Is this behavior well-defined?
}

Проблема, смущающая меня, - это если яэтот призыв к (app_ptr -> run_application)(app_ptr); приводит к UB.

«Статический тип» app_ptr, если struct application_t*, но «динамический тип» равен struct tcp_application_impl_t*.struct application_t и struct tcp_application_t несовместимы с N1570 6.2.7 (p1):

должно быть взаимно-однозначное соответствие между их членами, так что каждая пара соответствующих членовобъявлено с совместимыми типами

, что в данном случае явно не соответствует действительности.

Не могли бы вы дать ссылку на стандарт, объясняющий поведение?

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

Если «строгое правило псевдонимов» (N1570 6.5p7) интерпретируется просто как указание обстоятельств, при которых вещи могут иметь псевдонимы (что может показаться тем, что предполагали авторы, учитывая сноску 88, в которой говорится «Намерение этого списка»состоит в том, чтобы указать те обстоятельства, при которых объект может быть или не быть псевдонимом ") код, подобный вашему, не должен создавать проблем при условии, что во всех контекстах, когда к объекту обращаются с использованием l-значений двух разных типов, одно из задействованных l-значений является явно свежим производнымот другого.

Единственный способ, которым 6.5p7 может иметь какой-либо смысл, - это если операции с объектами, которые недавно были визуально получены из других объектов, распознаются как операции с оригиналами.Однако вопрос о том, когда признавать такой вывод, остается вопросом качества реализации, и считается, что рынок сможет лучше судить, чем Комитет, о том, что необходимо для того, чтобы что-то было «качественным» осуществлением, подходящим для некоторого конкретного случая.цель.

Если цель состоит в том, чтобы написать код, который будет работать на реализациях, настроенных на выполнение четкого замысла сноски 88, то следует быть уверенным, что объекты не имеют псевдонимов.Отстаивание этого требования может потребовать, чтобы один гарантировал, что компилятор может видеть, что указатели связаны друг с другом или что каждый из них был недавно получен из общего объекта в точке использования.Учитывая, например,

thing1 *p1 = unionArray[i].member1;
int v1 = p1->x;
thing2 *p2 = unionArray[j].member2;
p2->x = 31;
thing1 *p3 = unionArray[i].member1;
int v2 = p3->x;

каждый указатель будет использоваться в контексте, где он был недавно получен из unionArray, и, таким образом, не будет псевдонимов, даже если i==j.Компилятор, такой как «icc», не будет иметь проблем с таким кодом, даже с включенным -fstrict-aliasing, но, поскольку и gcc, и clang предъявляют требования 6.5p7 к программистам даже в случаях, не связанных с псевдонимами, они не будут обрабатывать его правильно.

Обратите внимание, что если бы код был:

thing1 *p1 = unionArray[i].member1;
int v1 = p1->x;
thing2 *p2 = unionArray[j].member2;
p2->x = 31;
int v2 = p1->x;

, то при втором использовании p1 будет псевдоним p2 в случаях, когда i==j, поскольку p2 будет обращаться к хранилищу, связанномус p1, через средства, не включающие p1, между временем p1 и последним временем его использования (таким образом, псевдоним p1).

Согласно авторам Стандарта,Дух C включает в себя принципы «Доверяй программисту» и «Не мешай программисту делать то, что ему нужно».Если нет особой необходимости справляться с ограничениями реализации, которая не особенно хорошо подходит для того, что вы делаете, следует ориентироваться на реализации, которые поддерживают Дух C способом, соответствующим его целям.Диалект -fstrict-aliasing, обработанный icc, или диалект -fno-strict-aliasing, обработанный icc, gcc и clang, должны подходить для ваших целей.-fstrict-aliasing диалекты gcc и clang должны быть признаны просто непригодными для ваших целей и не стоящими нацеливания.

0 голосов
/ 07 февраля 2019

Ваши две структуры не совместимы, так как они разных типов.Вы уже нашли главу «совместимые типы», которая определяет, что делает две структуры совместимыми.UB появляется позже, когда вы получаете доступ к этим структурам с указателем на неправильный тип, строгое нарушение псевдонимов согласно 6.5 / 7.

Очевидный способ решить эту проблему был бы следующим:

struct tcp_application_impl_t{
    struct application_t app;
    int client_fd;
    int socket_fd;
}

Теперь типы могут иметь псевдоним, поскольку tcp_application_impl_t является агрегатом, содержащим application_t среди своих членов.

Альтернатива, чтобы сделать это четко определенным, состоит в использовании подлого специального правила "union commonначальная последовательность ", найдена скрытой в C17 6.5.2.3/6:

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

Это позволит вам использовать ваши оригинальные типы так, как вы их объявили.Но где-то в том же модуле перевода вам нужно будет добавить фиктивную typedef для объединения, чтобы использовать вышеприведенное правило:

typedef union
{
  struct application_t app;
  struct tcp_application_impl_t impl;
} initial_sequence_t;

На самом деле вам не нужно использовать какой-либо экземпляр этого объединения, ему просто нужносидеть там видно.Это говорит компилятору, что этим двум типам разрешен псевдоним, поскольку их общая начальная последовательность идет.В вашем случае это означает указатели на функции, но не конечные переменные в tcp_application_impl_t.

Редактировать:

Отказ от ответственности.Обычная начальная уловка последовательности, по-видимому, немного противоречива, поскольку компиляторы делают с ней другие вещи, чем предполагал комитет.И, возможно, работает по-разному в C и C ++.См. структуры union «punning» с «общей начальной последовательностью»: почему C (99+), но не C ++, предусматривает «видимое объявление типа объединения»?

...