0

我有一个模块,我想对其客户隐藏其实现。

我选择声明一个不透明类型,它实际上是一个指向仅在实现中定义的结构的指针。

一切正常,除了我可以将零值分配给这种类型的变量,我想避免这种情况。

这是 C 中的一个示例。

头文件 foo.h

/* foo.h */
typedef struct foo *foo_t; /* <- sorry this was obviously flawed, the '*' was missing */


extern void foo_create( foo_t *t );
extern void foo_destroy( foo_t *t );
extern void foo_tile( foo_t x );

实现文件 foo.c

/* foo.c */

#include <stdlib.h>

#include "foo.h"

struct foo {
    int some_member;
};

void foo_create( foo_t *t )
{
    if ( *t==0 ) {
        *t = malloc( sizeof(struct foo) );         
    }
}

void foo_destroy( foo_t *t )
{
    if ( *t!=0 ) {
        free(*t);
        *t    = 0;
    }
}


void foo_tile( foo_t t )
{
    t->some_member++;
}

现在这是一个使用模块的示例客户端:bar.c:

#include "foo.h"

int main( int argc , char **argv )
{
    foo_t toe;

    foo_create( &toe );
    toe    = 0;  /* <-- How to make the compiler (gcc) refuse this? */
    toe    = 1;  /* <--- the compiler rejects this YAY!!            */
}

opaque 类型实际上是一个指向动态分配结构的指针;如果我将值 0 分配给它,我会发生内存泄漏,如果编译器拒绝将 0 分配给此不透明指针,则可以避免这种情况。

编译器不接受为指针分配非空值,因此我认为通过更多的努力可以实现零值。

是否可以禁用此分配?我怎样才能做到这一点?如果需要使用一些 C++ 或 gcc 特定的构造,我会同意的,尽管纯 C 解决方案会很好。

提前致谢。

4

3 回答 3

4

首先,您的 typedef 是错误的:(typedef struct foo foo_t;所以它是您的main,否则编译器将捕获对结构的分配)。

对于不透明类型,通常会执行以下操作:typedef struct foo *foo_t;. 否则,您toe将不会成为您发布的示例中的指针(这就是您必须使用 & 传递它的原因)。考虑到mallocin foo_create,我很确定您输入了错误的 typedef。

其次,问问自己,你将如何释放内存?通过使用清理函数 ( foo_destroy),对吗?并且用户应该将此指针传递给清理函数。

所以考虑一下:如果用户不知道给它分配一个整数,为什么她不知道清理呢?

编辑

Stéphane Gimenez 评论typedef struct foo foo_t 是 OP 想要的。我想强调一点

客户端对这种类型的对象唯一能做的就是获取它的地址,产生一个不透明的指针。

于 2011-08-10T18:49:41.393 回答
1

我不确定你能不能那样做。编译器会在 main() 中失败:

    toe    = 0;  /* <-- How to make the compiler (gcc) refuse this? */

它也会在 foo_destroy() 中失败:

    void foo_destroy( foo_t *t )
    {
        if ( *t!=0 ) {
            free(*t);
            *t    = 0;  /* compiler refuses this also */
        }
    }

您可以尝试直接从 foo_create() 返回分配的内存,而不是传入 foo_t 参数(模拟构造函数):

extern foo_t * foo_create( void );

foo_t * foo_create( void )
{
    foo_t * t;

    t = malloc( sizeof(struct foo) );  

    return(t);       
}

int main( int argc , char **argv )
{
    foo_t * toe;

    toe = foo_create();

    toe = 0; /* Clearly a memory leak via reassignment */
    ...
}
于 2011-08-10T20:58:43.230 回答
0

你想错了。您将如何启动本地 foo_t 变量?如果你输入

void bar(void)
{
  foo_t var;
}

那么 var 将包含垃圾。使其干净的唯一方法是键入

void bar(void)
{
  foo_t var = NULL;
}

或者0,如果您愿意,但这应该会发出警告。因此,您问题中的代码不安全,它可能会崩溃。

您可以做的是将非空属性添加到 foo_tile,即:

void foo_tile( foo_t t ) __attribute__((nonnull(1)));

这将阻止foo_tile(NULL);甚至在某些编译器中

foo_t var = NULL;
foo_tile(var);

尽管它可能只会使其成为警告,而不是硬错误。

于 2011-08-10T20:54:12.173 回答