2014-08-27 9 views
7

Я экспериментировал с C11 и VLAs, пытаясь объявить структурную переменную в стек только с неполным объявлением. Цель состоит в том, чтобы создать механизм для создания переменной какого-либо типа структуры без отображения внутренних элементов (например, идиомы PIMPL), но без необходимости создавать переменную в куче и возвращать указатель на нее. Кроме того, если макет структуры изменяется, я не хочу перекомпилировать каждый файл, который использует структуру.Полная инкапсуляция без malloc

мне удалось запрограммировать следующие:

private.h:

#ifndef PRIVATE_H_ 
#define PRIVATE_H_ 

typedef struct A{ 
    int value; 
}A; 

#endif /* PRIVATE_H_ */ 

public.h:

#ifndef PUBLIC_H_ 
#define PUBLIC_H_ 

typedef struct A A; 

size_t A_getSizeOf(void); 

void A_setValue(A * a, int value); 

void A_printValue(A * a); 

#endif /* PUBLIC_H_ */ 

implementation.c:

#include "private.h" 
#include "stdio.h" 

size_t A_getSizeOf(void) 
{ 
    return sizeof(A); 
} 

void A_setValue(A * a, int value) 
{ 
    a->value = value; 
} 

void A_printValue(A * a) 
{ 
    printf("%d\n", a->value); 
} 

магистральный .c:

#include <stdalign.h> 
#include <stddef.h> 

#include "public.h" 

#define createOnStack(type, variable) \ 
    alignas(max_align_t) char variable ## _stack[type ## _getSizeOf()]; \ 
    type * variable = (type *)&variable ## _stack 

int main(int argc, char *argv[]) { 
    createOnStack(A, var); 

    A_setValue(var, 5335); 
    A_printValue(var); 
} 

Я проверил этот код и, похоже, сработал. Однако я не уверен, что я что-то пропускаю (например, наложение, выравнивание или что-то в этом роде), которые могут быть опасными или неспортивными или могут повредить производительность. Также я хочу знать, есть ли лучшие (переносные) решения этой проблемы в C.

+0

Вы не можете сделать это без перекомпиляции, когда макет структуры изменится, поскольку sizeof будет оптимизирован для постоянной времени компиляции, если вы используете VLA или alloca – Vality

+2

@Vality: посмотрите еще раз на код - это будет оптимизация времени ссылки ; Разум Mabus должен звучать – Christoph

+0

@Mabus благодарит за публикацию этого. Я не думал использовать VLA символов для хранения других типов. –

ответ

4

Это, конечно, нарушает действующие правила набора текста (например, строгий псевдоним), потому что язык C не позволяет объекту tye char [] быть доступ через указатель, который не имеет этого типа (или совместимый).

Вы могли отключить строгий анализ наложения спектров с помощью флагов компилятора, как -fno-strict-aliasing или атрибуты, как

#ifdef __GNUC__ 
#define MAY_ALIAS __attribute__((__may_alias__)) 
#else 
#define MAY_ALIAS 
#endif 

(благодарность R .. за указание последнего), но даже если вы не делаете поэтому на практике все должно работать нормально, пока вы используете только имя переменной для инициализации введенного указателя.

Лично я бы упростить заявления на что-то вдоль линий

#define stackbuffer(NAME, SIZE) \ 
    _Alignas (max_align_t) char NAME[SIZE] 

typedef struct Foo Foo; 
extern const size_t SIZEOF_FOO; 

stackbuffer(buffer, SIZEOF_FOO); 
Foo *foo = (void *)buffer; 

В качестве альтернативы будет использовать нестандартную alloca(), но что «функция» имеет свой собственный набор вопросов.

+1

Если вы согласны с GNU-C-совместимым компилятором с '-fno-strict-aliasing', то вместо того, чтобы разрушить оптимизацию всей программы с этим флагом, вы должны поместить' __attribute __ ((__ may_alias __)) 'on тип 'Foo'. Это должно обеспечить результаты только для одного типа. –

+0

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

+0

@Mabus: указатель на char может псевдонимом что-либо, но это противоположный случай - указатель на 'Foo' сглаживание массива символов; лучше подумать с точки зрения эффективных типов: блок памяти 'buffer' имеет эффективный тип' char [] ', но вы обращаетесь к нему как' Foo' – Christoph

1

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

Я хочу, чтобы клиенты структуры не обращались напрямую к полям, чтобы упростить рассуждение об их состоянии и легче написать надежные проектные контракты. Я также предпочел бы избежать выделения небольших структур в кучу. Но я не могу позволить себе публичный интерфейс C11 - большая часть радости от C заключается в том, что почти любой код знает, как разговаривать с C89.

С этой целью рассмотрим адекватный код приложения:

#include "opaque.h" 
int main(void) 
{ 
    opaque on_the_stack = create_opaque(42,3.14); // constructor 
    print_opaque(&on_the_stack); 
    delete_opaque(&on_the_stack); // destructor 
    return 0; 
} 

Непрозрачный заголовок довольно неприятный, но не совсем абсурдно.Предоставление функций создания и удаления в основном заключается в согласованности с структурами, где на самом деле имеет место вызов деструктора.

/* opaque.h */ 
#ifndef OPAQUE_H 
#define OPAQUE_H 

/* max_align_t is not reliably available in stddef, esp. in c89 */ 
typedef union 
{ 
    int foo; 
    long long _longlong; 
    unsigned long long _ulonglong; 
    double _double; 
    void * _voidptr; 
    void (*_voidfuncptr)(void); 
    /* I believe the above types are sufficient */ 
} alignment_hack; 

#define sizeof_opaque 16 /* Tedious to keep up to date */ 
typedef struct 
{ 
    union 
    { 
    char state [sizeof_opaque]; 
    alignment_hack hack; 
    } private; 
} opaque; 
#undef sizeof_opaque /* minimise the scope of the macro */ 

void print_opaque(opaque * o); 
opaque create_opaque(int foo, double bar); 
void delete_opaque(opaque *); 
#endif 

Наконец-то реализация, которую можно использовать с C11, поскольку это не интерфейс. _Static_assert (alignof ...) особенно обнадеживает. Несколько уровней статических функций используются, чтобы указать на очевидное уточнение создания слоев wrap/unwrap. В значительной степени весь беспорядок поддается генерации кода.

#include "opaque.h" 

#include <stdalign.h> 
#include <stdio.h> 

typedef struct 
{ 
    int foo; 
    double bar; 
} opaque_impl; 

/* Zero tolerance approach to letting the sizes drift */ 
_Static_assert(sizeof (opaque) == sizeof (opaque_impl), "Opaque size incorrect"); 
_Static_assert(alignof (opaque) == alignof (opaque_impl), "Opaque alignment incorrect"); 

static void print_opaque_impl(opaque_impl *o) 
{ 
    printf("Foo = %d and Bar = %g\n",o->foo,o->bar); 
} 

static void create_opaque_impl(opaque_impl * o, int foo, double bar) 
{ 
    o->foo = foo; 
    o->bar = bar; 
} 

static void create_opaque_hack(opaque * o, int foo, double bar) 
{ 
    opaque_impl * ptr = (opaque_impl*)o; 
    create_opaque_impl(ptr,foo,bar); 
} 

static void delete_opaque_impl(opaque_impl *o) 
{ 
    o->foo = 0; 
    o->bar = 0; 
} 

static void delete_opaque_hack(opaque * o) 
{ 
    opaque_impl * ptr = (opaque_impl*)o; 
    delete_opaque_impl(ptr); 
} 

void print_opaque(opaque * o) 
{ 
    return print_opaque_impl((opaque_impl*)o); 
} 

opaque create_opaque(int foo, double bar) 
{ 
    opaque tmp; 
    unsigned int i; 
    /* Useful to zero out padding */ 
    for (i=0; i < sizeof (opaque_impl); i++) 
    { 
     tmp.private.state[i] = 0; 
    } 
    create_opaque_hack(&tmp,foo,bar); 
    return tmp; 
} 

void delete_opaque(opaque *o) 
{ 
    delete_opaque_hack(o); 
} 

Недостатков я вижу себя:

  1. Изменение размера определяют вручную будет раздражающим
  2. Отливка должна препятствовать оптимизации (я не проверял это еще)
  3. Это может нарушить строгую наложение указателя. Необходимо перечитать спецификацию.

Я обеспокоен случайным использованием неопределенного поведения. Меня также интересует общая обратная связь по вышесказанному, или же она выглядит как надежная альтернатива изобретательскому методу VLA в вопросе.

+0

Наиболее очевидной проблемой, которую я вижу, является то, что если вы измените sizeof_opaque, вы не сможете иметь двоичную совместимость. С реализацией VLA вы можете изменить размер структуры, и скомпилированные программы все еще работают (предположим, что вы программируете общую библиотеку). Также обратите внимание, что GCC и другие компиляторы имеют атрибут, похожий на alignas (aligned), если вы предпочитаете писать код C99 с расширениями. Кроме того, когда вы создаете структуру внутри другого вне вашей библиотеки, вам придется в этом случае malloc, потому что вы не знаете размер во время компиляции. – Mabus

+0

@Mabus Это правда, изменение размера нарушило бы двоичную совместимость. VLA или malloc, вероятно, являются единственными обходными решениями. С яркой стороны состав непрозрачных структур прекрасно работает, когда размер фиксируется, без кучи. Всегда компромисс! Бинарная совместимость библиотек - это то, что мне нужно дать (много) больше. –

 Смежные вопросы

  • Нет связанных вопросов^_^