GCC 可以警告我修改 C99 中 const 结构的字段吗?
Can GCC warn me about modifying the fields of a const struct in C99?
我在尝试编写 const 正确的代码时偶然发现了一个小问题。
我本来想写一个函数,它接受一个指向 const 结构的指针,告诉编译器 "please tell me if I am modifying the struct, because I really do not want to"。
突然想到编译器会允许我这样做:
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
是否可以解决这个问题?
否,除非您将结构定义更改为:
struct A
{
const char *ptrChar;
};
另一个保持旧结构定义不变的复杂解决方案是定义一个具有相同成员的新结构,其相关指针成员设置为:指向常量类型。然后您正在调用的函数被更改为采用新结构。定义了一个包装函数,它采用旧结构,逐个成员复制到新结构并将其传递给函数。
这是实现与接口的示例,或者 "information hiding" —— 或者更确切地说是非隐藏 ;-) —— 问题。在 C++ 中,可以简单地将指针设置为私有并定义合适的 public const 访问器。或者可以用访问器定义一个抽象 class —— 一个 "interface" 。适当的结构将实现它。不需要创建结构实例的用户只需要看到接口。
在 C 中,可以通过定义一个函数来模拟这一点,该函数将指向结构的指针作为参数,returns 将指向 const char 的指针作为参数。对于不创建这些结构实例的用户,甚至可以提供一个 "user header" ,它不会泄漏结构的实现,而只会定义获取(或返回,如工厂)指针的操作函数。这使结构成为不完整的类型(因此只能使用指向实例的指针)。这种模式有效地模拟了 C++ 在幕后使用 this
指针所做的事情。
这是C语言的已知问题,无法避免。毕竟,您不是在修改结构,而是通过从结构中获得的非 const
限定指针修改单独的对象。 const
语义最初是围绕将物理上不可写的内存区域标记为常量的需要而设计的,而不是围绕防御性编程的任何问题。
也许如果您决定使用 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
只能使用此结构的指针或对象,而不能同时使用两者。
如果需要,一种解决此问题的方法是对同一对象使用两种不同的类型:一种 read/write 类型和一种只读类型。
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 的 public 界面:
inline void A_modify (A* a)
{
modify(&a->rw);
}
inline void A_print (const A* a)
{
print(&a->ro);
}
使用此方法,A
现在可以实现为不透明类型,以隐藏调用者的实现。
我们可以隐藏某些 "accessor" 函数背后的信息:
// 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; }
Can GCC warn me about modifying the fields of a const struct in C99?
您没有修改 const 结构的字段。
结构 A 的值包含指向非常量字符的指针。 ptrA 是指向 const struct A 的指针。因此您不能更改 *ptrA 处的 struct A 值。所以你不能在 (*ptrA).Char aka ptrA->ptrChar 处更改指向 char 的指针。但是您正在更改 ptrA->ptrChar 指向的值,即 *(ptrA->Char) aka ptrA->Char[0] 处的值。这里唯一的常量是 struct As 并且你没有改变 struct A 那么确切的是 "not desired"?
如果您不想允许更改结构 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;
}
编译器会报错。
我在尝试编写 const 正确的代码时偶然发现了一个小问题。
我本来想写一个函数,它接受一个指向 const 结构的指针,告诉编译器 "please tell me if I am modifying the struct, because I really do not want to"。
突然想到编译器会允许我这样做:
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
是否可以解决这个问题?
否,除非您将结构定义更改为:
struct A
{
const char *ptrChar;
};
另一个保持旧结构定义不变的复杂解决方案是定义一个具有相同成员的新结构,其相关指针成员设置为:指向常量类型。然后您正在调用的函数被更改为采用新结构。定义了一个包装函数,它采用旧结构,逐个成员复制到新结构并将其传递给函数。
这是实现与接口的示例,或者 "information hiding" —— 或者更确切地说是非隐藏 ;-) —— 问题。在 C++ 中,可以简单地将指针设置为私有并定义合适的 public const 访问器。或者可以用访问器定义一个抽象 class —— 一个 "interface" 。适当的结构将实现它。不需要创建结构实例的用户只需要看到接口。
在 C 中,可以通过定义一个函数来模拟这一点,该函数将指向结构的指针作为参数,returns 将指向 const char 的指针作为参数。对于不创建这些结构实例的用户,甚至可以提供一个 "user header" ,它不会泄漏结构的实现,而只会定义获取(或返回,如工厂)指针的操作函数。这使结构成为不完整的类型(因此只能使用指向实例的指针)。这种模式有效地模拟了 C++ 在幕后使用 this
指针所做的事情。
这是C语言的已知问题,无法避免。毕竟,您不是在修改结构,而是通过从结构中获得的非 const
限定指针修改单独的对象。 const
语义最初是围绕将物理上不可写的内存区域标记为常量的需要而设计的,而不是围绕防御性编程的任何问题。
也许如果您决定使用 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
只能使用此结构的指针或对象,而不能同时使用两者。
如果需要,一种解决此问题的方法是对同一对象使用两种不同的类型:一种 read/write 类型和一种只读类型。
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 的 public 界面:
inline void A_modify (A* a)
{
modify(&a->rw);
}
inline void A_print (const A* a)
{
print(&a->ro);
}
使用此方法,A
现在可以实现为不透明类型,以隐藏调用者的实现。
我们可以隐藏某些 "accessor" 函数背后的信息:
// 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; }
Can GCC warn me about modifying the fields of a const struct in C99?
您没有修改 const 结构的字段。
结构 A 的值包含指向非常量字符的指针。 ptrA 是指向 const struct A 的指针。因此您不能更改 *ptrA 处的 struct A 值。所以你不能在 (*ptrA).Char aka ptrA->ptrChar 处更改指向 char 的指针。但是您正在更改 ptrA->ptrChar 指向的值,即 *(ptrA->Char) aka ptrA->Char[0] 处的值。这里唯一的常量是 struct As 并且你没有改变 struct A 那么确切的是 "not desired"?
如果您不想允许更改结构 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;
}
编译器会报错。