代码之家  ›  专栏  ›  技术社区  ›  Some Name

C中的接口

  •  2
  • Some Name  · 技术社区  · 6 年前

    我正在设计一个应用程序,遇到了一个实现问题。我有以下结构定义:

    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); 耶鲁大学。

    应用程序ptr if的“静态类型” struct application_t* 但是“动态类型”是 struct tcp_application_impl_t* . 这个 struct application_t struct tcp_application_t 与N1570 6.2.7(p1)不兼容:

    其成员之间应有一对一的通信 每对相应的成员声明为兼容 类型

    在这种情况下显然是不正确的。

    你能提供一个参考标准解释行为吗?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Lundin    6 年前

    您的两个结构不兼容,因为它们是不同的类型。您已经找到了“兼容类型”一章,该章定义了使两个结构兼容的原因。稍后,当您使用指向错误类型的指针访问这些结构时,将出现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 在其成员中。

    另一种方法是使用隐藏在C17 6.5.2.3/6中的“联合公共初始序列”的秘密特殊规则:

    为了简化工会的使用,有一项特别保证:如果工会包含 几个共享同一初始序列的结构(见下文),以及 对象当前包含这些结构之一,允许检查 其中任何一个的初始部分 是可见的。两个结构共用一个 公共初始序列 如果对应成员 对于一个或多个初始成员的序列,具有兼容的类型(对于位字段,宽度相同)。

    这将允许您在声明原始类型时使用它们。但在同一翻译单元的某个地方,您必须添加一个虚拟联合typedef才能使用上述规则:

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

    你不需要实际使用这个联合的任何实例,它只需要在那里可见。这告诉编译器这两种类型可以别名,只要它们的公共初始序列是这样的。在您的例子中,它意味着函数指针,而不是后面的变量 TCP应用程序 .

    编辑:

    免责声明。常见的初始序列技巧显然有点争议,编译人员使用它做的事情比委员会预期的要多。在C和C++中可能不同。参见 union 'punning' structs w/ "common initial sequence": Why does C (99+), but not C++, stipulate a 'visible declaration of the union type'?

        2
  •  1
  •   supercat    6 年前

    如果“严格的别名规则”(N1570 6.5P7)仅仅被解释为规定了事物可以别名的情况(这似乎是作者的意图,给出脚注88,说明“此列表的目的是指定对象可以或不可以别名的情况”),那么像您这样的代码就不应构成任何问题。LEM规定,在使用两种不同类型的lvalue访问对象的所有上下文中,所涉及的lvalue之一显然是从另一个lvalue派生而来的。

    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别名 由ICC处理的方言,或 -fno-strict-aliasing 由ICC、GCC和CLANG处理的方言应适合您的目的。这个 -fstrict别名 GCC和Clang的方言应该被认为根本不适合你的目的,不值得瞄准。