Указатель на структуру const может быть изменен?

У меня есть структура, которую я хочу передать некоторому внешнему c-коду через некоторую функцию обратного вызова, которую они регистрируют в моей программе. Однако я хочу передать эту структуру как доступную только для чтения. Меня беспокоит то, что они все еще могут изменять структуры, которые у меня есть указатель внутри исходной структуры, которую я передаю. Разъяснение с небольшим примером ниже:

struct s1 {
    int a;
    int b;
};

struct s2 {
    int x;
    struct s1 *y;
};

void f(const struct s2 *o)
{
    //o->x=10; //error
    o->y->a=20; //no error
    o->y->b=30; //no error
}

int main()
{
    struct s1 o1 = {10, 20};
    struct s2 o2 = {30, &o1};
    f(&o2);
}

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

Ответ 1

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

Сосредоточьтесь на приведенном ниже коде и проверьте:

  • struct s1 имеет только объявление вперед, поэтому вы можете сделать указатель на него в struct s2.
  • Фактическая реализация struct s1 находится в mylib.c поэтому все участники видны только вашей библиотеке, а не пользователю.
  • Getters и seters реализованы для установки/чтения значения для этих скрытых членов, поскольку только ваша библиотека имеет доступ к членам, что делает его полностью скрытым от пользователя.
  • Это заставляет его использовать ваши функции.

mylib.h:

#ifndef __MYLIB_H
#define __MYLIB_H

//Create forward declaration only
//Implementation is in .c file
struct s1;

//Create user structure
struct s2 {
  int x;
  struct s1* y;
};

int get_a_from_s1(struct s2* s);
void set_a_to_s1(struct s2* s, int a);

#endif /* __MYLIB_H */

mylib.c:

#include "mylib.h"

//Now implement structure
struct s1 {
  int a, b;
};

//Make getter
int
get_a_from_s1(struct s2* s) {
  return s->y->a;
}

//Make setter
void
set_a_to_s1(struct s2* s, int a) {
  s->y->a = a;
}

main.c:

#include <stdio.h>
#include "mylib.h"

int main(void) {
  struct s2 s;
  int a;

  ....

  s.y->a = 5; //error

  //Set s1.a value from s2 structure
  set_a_to_s1(&s, 10); //OK

  //To view members of s1 inside s2, create member functions
  a = get_a_from_s1(&s); //OK

  printf("a: %d\r\n", a);

  return 0;
}

Конечно, убедитесь, что ->y не является NULL или у вас есть неопределенное поведение.

Ответ 2

Ты не можешь. Даже если вы передадите struct s2 по значению, вы получите в функции указатель на не const struct s1, просто потому, что это то, что содержит s2 по его определению.

И как только у вас есть указатель на объект non const, вы можете изменить этот объект. То, что я имею в виду здесь и что другие ответы означают, заключается в том, что это не проблема языка - более точно, язык для вас здесь ничего не может, но проблема дизайна. Если по какой-либо причине неприемлемо, что struct s1 может быть изменена с f вам нужно найти другой дизайн, в котором вы не передадите ему не const-указатель, будь он членом структуры const или нет. Здесь простой способ состоял бы в том, чтобы передать отдельные члены:

void f(int x, const struct s1 *y) {
    y->a = 20;  // error
}

Возможно, это не так, как вы ожидаете, но это лучшее, что я могу сказать для языка C.

Ответ 3

Вы можете изменить второе объявление структуры следующим образом:

struct s2 {
    int x;
    struct s1 const *y;
};

Добавленный const гарантирует, что y доступен только для чтения.

Ответ 4

Я бы написал еще одну функцию, которая принимает в const struct s1 * для изменения значений s1.

Ответ 5

В f() const struct s2 *o означает, что в struct s2 указывается o, значения ее членов, т.е. x и y не могут быть изменены.

С помощью o->y->a=20 вы не изменяете значение y. Вы просто используете этот адрес для изменения члена структуры, на который указывают значения y и y, которые являются адресом, остаются неизменными.

Таким образом, эти 2 строки не будут давать никаких ошибок.

Я не вижу возможности избежать этого, если вы не можете сделать определение struct s2

struct s2 {
    int x;
    const struct s1 *y;
};

в этом случае y является указателем на константу struct s1.

См. Правило спирали и посетите веб-сайт cdecl.