Может ли 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
Можно ли обойти эту проблему?
Ответы
Ответ 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;
}
Который получит ошибку от компилятора.