Вставить между указателем struct в C

Пожалуйста, рассмотрите следующий код.

typedef struct{
    int field_1;
    int field_2;
    int field_3;
    int field_4;

    uint8_t* data;
    uint32_t data_size;
} my_struct;

void ext_function(inalterable_my_struct* ims, ...);

Я хочу разрешить ext_function (написанный третьей стороной) изменять только field_3 и field_4 в my_struct. Поэтому я делаю следующее:

typedef struct{
    const int field_1;
    const int field_2;
    int field_3;
    int field_4;

    const uint8_t* data;
    const uint32_t data_size;
} inalterable_my_struct;

void ext_function(inalterable_my_struct* ims, ...);

Безопасно ли указывать указатели между my_struct и inalterable_my_struct перед вызовом ext_function (как показано ниже)?

void call_ext_function(my_struct* ms){
    inalterable_my_struct* ims = (inalterable_my_struct*)ms;
    ext_function(ims, ...);
}

Ответы

Ответ 1

Я не думаю, что это хорошая идея.

Вызываемая функция всегда может отбрасывать любые const: ness и изменять данные, если она хочет.

Если вы можете контролировать контрольные точки, было бы лучше создать копию и вызвать функцию с указателем на копию, а затем скопировать два поля, о которых вы заботитесь:

void call_ext_function(my_struct* ms)
{
    my_struct tmp = *ms;
    ext_function(&tmp, ...);
    ms->field_3 = tmp.field_3;
    ms->field_4 = tmp.field_4;
}

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

Возможно, вам придется подделать данные на основе указателей, если функция коснется его.

Ответ 2

В соответствии со стандартом C99 два struct не будут иметь совместимые типы, даже если их объявления были идентичны. Из раздела 6.7.7.5:

ПРИМЕР 2 После деклараций

typedef struct s1 { int x; } t1, *tp1;
typedef struct s2 { int x; } t2, *tp2;

type t1 и тип, на который указывает tp1, совместимы. Тип t1 также совместим с типом struct s1, но не совместим с типами struct s2, t2, с типом, на который указывает tp2 или int.

Кроме того, два типа с разными квалификаторами не считаются совместимыми:

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

Более чистый подход состоял бы в том, чтобы полностью скрыть ваш struct, замените его неясным дескриптором (a typedef поверх void*) и предоставите функции для управления элементами struct. Таким образом, вы сохранили бы полный контроль над структурой вашего struct: вы могли бы переименовать свои поля по своему усмотрению, изменить макет столько и сколько захотите, изменить основные типы полей и сделать другие вещи что вы обычно избегаете, когда ваш макет struct известен вашим клиентам.

Ответ 3

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

Решение, предоставляемое разматыванием, является очень хорошим. Альтернативным (и более очевидным) решением было бы разбить структуру на две более мелкие части.

typedef struct{
    const int field_1;
    const int field_2;
    const uint8_t* data;
    const uint32_t data_size;
} inalterable_my_struct;

typedef struct{
    int field_3;
    int field_4;
} my_struct;

void ext_function(const inalterable_my_struct* const ims, my_struct* ms ...);

Я сделал указатель также постоянным в вышеуказанном вызове, но это необязательно.

Ответ 4

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

Вот почему это ничего не изменит:

$ cat foo.c
struct foo {
    const int a;
    int b;
};

void
foo(struct foo *foo)
{
    foo->a = 1;
}
$ cc -c foo.c
foo.c: In function ‘foo’:
foo.c:9: error: assignment of read-only member ‘a’
$ cc -Dconst= -c foo.c
$ 

Ответ 5

Запись в члены, которые раньше были const, может быть небезопасной. Возможно, они были помещены в постоянное запоминающее устройство компилятором/компоновщиком и операционной системой. См. Как доступна память только для чтения в C?.