Подтвердить что ты не робот

Может ли GCC предупредить меня об изменении полей структуры const в C99?

Я наткнулся на небольшую проблему, пытаясь сделать const-правильный код.

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

Мне вдруг пришло в голову, что компилятор позволит мне это сделать:

struct A
{
    char *ptrChar;
};

void f(const struct A *ptrA)
{
    ptrA->ptrChar[0] = 'A'; // NOT DESIRED!!
}

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

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

gcc -std=c99 -Wall -Wextra -pedantic test.c

Можно ли обойти эту проблему?

4b9b3361

Ответ 1

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

typedef struct
{
  char *ptrChar;
} A_rw;

typedef struct
{
  const char* ptrChar;
} A_ro;


typedef union
{
  A_rw rw;
  A_ro ro;  
} A;

Если функции необходимо изменить объект, он принимает тип чтения-записи как параметр, в противном случае он принимает тип только для чтения.

void modify (A_rw* a)
{
  a->ptrChar[0] = 'A';
}

void print (const A_ro* a)
{
  puts(a->ptrChar);
}

Чтобы упростить интерфейс вызывающего абонента и сделать его согласованным, вы можете использовать функции-обертки в качестве открытого интерфейса для вашего ADT:

inline void A_modify (A* a)
{
  modify(&a->rw);
}

inline void A_print (const A* a)
{
  print(&a->ro);
}

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

Ответ 2

Это пример реализации по сравнению с интерфейсом или "скрытие информации" - или, скорее, не скрывающий;-) - вопрос. В С++ можно было бы просто указать указатель private и определить подходящие общедоступные устройства доступа. Или можно определить абстрактный класс - "интерфейс" - с аксессуаром. Собственно эта структура будет реализована. Пользователям, которым не нужно создавать экземпляры структур, нужно будет только увидеть интерфейс.

В C можно эмулировать это путем определения функции, которая берет указатель на struct как параметр и возвращает указатель на const char. Для пользователей, которые не создают экземпляры этих структур, можно даже предоставить "заголовок пользователя", который не протекает по реализации структуры, но определяет только манипуляции с функциями (или возвращающими, например, factory) указатели. Это оставляет структуру неполным (так что могут использоваться только указатели на экземпляры). Этот шаблон эффективно эмулирует то, что С++ делает за кулисами с помощью указателя this.

Ответ 3

Это известная проблема языка C, и ее нельзя избежать. В конце концов, вы не изменяете структуру, вы изменяете отдельный объект с помощью const -qualified указателя, который вы получили из структуры. const semantic изначально был спроектирован вокруг необходимости отмечать области памяти как постоянные, которые физически не доступны для записи, а не вокруг проблем для защитного программирования.

Ответ 4

Возможно, если вы решите использовать C11, вы можете реализовать общий макрос, который ссылается либо на константу, либо на переменную версию того же члена (вы также должны включить объединение в свою структуру). Что-то вроде этого:

struct A
{
    union {
        char *m_ptrChar;

        const char *m_cptrChar;
    } ;
};

#define ptrChar_m(a) _Generic(a, struct A *: a->m_ptrChar,        \
                                 const struct A *: a->m_cptrChar)//,  \
                                 //struct A: a.m_ptrChar,        \
                                 //const struct A: a.m_cptrChar)

void f(const struct A *ptrA)
{
    ptrChar_m(ptrA) = 'A'; // NOT DESIRED!!
}

Союз создает 2 интерпретации для одного члена. m_cptrChar - это указатель на константу char и m_ptrChar на непостоянную. Затем макрос решает, к какой ссылке следует обращаться в зависимости от типа этого параметра.

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

Ответ 5

Мы могли бы скрыть информацию за некоторыми функциями "доступа":

// header
struct A;           // incomplete type
char *getPtr(struct A *);
const char *getCPtr(const struct A *);

// implementation
struct A { char *ptr };
char *getPtr(struct A *a) { return a->ptr; }
const char *getCPtr(const struct A *a) { return a->ptr; }

Ответ 6

Нет, если вы не измените определение структуры на:

struct A
{
    const char *ptrChar;
};

Еще одно сложное решение, которое сохраняет прежнее определение структуры, состоит в том, чтобы определить новую структуру с идентичными членами, чьи соответствующие элементы указателя имеют значение: указывает на тип const. Затем функция, которую вы вызываете, изменяется, чтобы принять новую структуру. Определяется функция-оболочка, которая принимает старую структуру, выполняет член по членной копии в новую структуру и передает ее функции.

Ответ 7

Может ли GCC предупредить меня об изменении полей структуры const в C99?

Вы не изменяете поля структуры const.

Значение struct A содержит указатель на неконстантный char. ptrA является указателем на const const A. Поэтому вы не можете изменить значение struct A на * ptrA. Поэтому вы не можете изменить указатель на char на (* ptrA).Char aka ptrA- > ptrChar. Но вы меняете значение, где ptrA- > ptrChar указывает, т.е. Значение в * (ptrA → Char) aka ptrA → Char [0]. Единственными константами здесь являются struct As и вы не меняете структуру A, так что точно "нежелательно"?

Если вы не хотите разрешать изменение значения, в котором точки поля структуры A char (через эту структуру A), используйте

struct A
{
    const char *ptrChar; // or char const *ptrChar;
};

Но, возможно, то, что вы думаете, что вы делаете в f, похоже на

void f(const struct A *ptrA)
{
    const char c = 'A';
    ptrA->ptrChar = &c;
}

Который получит ошибку от компилятора.