Как отключить целочисленное присваивание (0) непрозрачной переменной? - PullRequest
0 голосов
/ 10 августа 2011

У меня есть модуль, реализацию которого я хочу скрыть от своих клиентов.

Я решил объявить непрозрачный тип, который фактически является указателем на структуру, которая должна быть определена только в реализации.

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

Вот пример на 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!!            */
}

Непрозрачный тип на самом деле является указателем на динамически размещенную структуру; Если я присваиваю ему значение 0, я получаю утечку памяти, которой можно избежать, если компилятор отклонит присвоение 0 этому непрозрачному указателю.

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

Можно ли отключить это назначение? Как я могу этого достичь? Если потребуется использование некоторых конструкций, специфичных для C ++ или gcc, я согласен с этим, хотя было бы неплохо чисто C-решение.

Заранее спасибо.

Ответы [ 3 ]

4 голосов
/ 10 августа 2011

Прежде всего, ваш typedef неверен: typedef struct foo foo_t; (и поэтому ваш main, иначе компилятор будет перехватывать назначения для структур).

Для непрозрачных типов принято делать что-то вроде: typedef struct foo *foo_t;. В противном случае ваш toe не будет указателем в примере, который вы опубликовали (вот почему вы должны были передать его с &). Учитывая malloc в foo_create, я почти уверен, что вы ввели неверный typedef.

Во-вторых, спросите себя, как вы собираетесь освободить память? С помощью функции очистки (foo_destroy), верно? И пользователь должен передать этот указатель в функцию очистки.

Так что подумайте: если пользователь не имеет ни малейшего понятия, чтобы присвоить ему целое число, почему бы ему не быть настолько неосведомленным, чтобы забыть очистить ?

EDIT

Стефан Гименес прокомментировал typedef struct foo foo_t - это то, что ОП хочет . Я хотел бы подчеркнуть , что :

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

1 голос
/ 11 августа 2011

Я не уверен, что вы можете сделать это таким образом. Компилятор потерпит неудачу в 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 */
    ...
}
0 голосов
/ 11 августа 2011

Вы думаете об этом неправильно.Как бы вы инициировали локальную переменную 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);

, хотя, скорее всего, это будет просто предупреждением, а не серьезной ошибкой.

...