记录: C 语言标识符查找与命名空间以及结构体定义引发的问题
版权声明:署名-非商业性使用-相同方式共享
多年来写惯 C++ 了, 突然遇到一个 C 语言的问题, 还真有点摸不着头脑
在此感谢 Sakura酱
, 喵
二位大佬的解惑, 也不禁感叹 C 语言的博大精深与自我知识的匮乏
现将问题记录如下
问题
// test.c
struct person_t {
unsigned char* name;
int age;
};
typedef struct person_t* pperson_t;
#if 1
// interface
pperson_t person_new(unsigned char* name, unsigned int age);
int person_age(person_t* self);
// implementation
pperson_t person_new(unsigned char* name, unsigned int age) {
return 0;
}
int person_age(person_t* self) {
return 0;
}
int main() {
return 0;
}
#else
// interface
pperson_t person_new(unsigned char* name, unsigned int age);
int person_age(pperson_t self);
// implementation
pperson_t person_new(unsigned char* name, unsigned int age) {
return 0;
}
int person_age(pperson_t self) {
return 0;
}
#endif
int main() {
return 0;
}
上面的代码在 msvc 中编译会报下面的错误:
cl.exe test.c
用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.29.30153 版
版权所有(C) Microsoft Corporation。保留所有权利。
test.c
test.c(11): error C2143: 语法错误: 缺少“)”(在“*”的前面)
test.c(11): error C2143: 语法错误: 缺少“{”(在“*”的前面)
test.c(11): error C2059: 语法错误:“)”
test.c(18): error C2143: 语法错误: 缺少“)”(在“*”的前面)
test.c(18): error C2143: 语法错误: 缺少“{”(在“*”的前面)
test.c(18): error C2059: 语法错误:“)”
test.c(18): error C2054: 在“self”之后应输入“(”
但只要我将 #if 1 修改为 #if 0, 就可以编译通过了, 它们之间的区别仅仅在于
第一个使用 person_t* self
第二个使用 pperson_t self
但两者其实是同一个类型: typedef struct person_t* pperson_t;
这是哪里出现了问题?
另外 gcc 也会出现同样的错误, 而以 C++ 模式编译都一切正常!
gcc test.c
test.c:11:22: error: unknown type name 'person_t'; did you mean 'pperson_t'?
11 | int person_age(person_t* self);
| ^~~~~~~~
| pperson_t
test.c:18:16: error: unknown type name 'person_t'; did you mean 'pperson_t'?
18 | int person_age(person_t* self) {
| ^~~~~~~~
| pperson_t
原因及解决方案
在 C 程序中遇到标识符时,会查找并定位引入该标识符的声明(在当前作用域内)。若同一标识符的多个声明属于不同的 “命名空间” 类别,则 C 允许它们同时存在于作用域内(不包含 C23 引入的内容):
- 标号命名空间:所有声明为标号的标识符。
- 标签名:所有声明为结构体、联合体及枚举类型名称的标识符,注意它们共享同一命名空间。
- 成员名:所有声明为结构体或联合体的成员的标识符。
- 通常标识符: 所有其他标识符如 函数名、对象名、typedef 名、枚举常量。
在查找时,根据使用方式确定标识符所属的命名空间
参考 C 语言 查找与命名空间: https://zh.cppreference.com/w/c/language/name_space
struct A { int c; }; // 名称 A 引入标签命名空间
typedef struct A A; // 第一个 A 从标签命名空间中查找, 然后第二个 A 则将名称 A 引入通常命名空间
struct A* p; // A 从标签命名空间中查找
A* q; // A 从通常命名空间中查找
q->c; // c 从A成员名命名空间中查找
goto __LABEL1; // __LABEL1 从标号命名空间中查找
根据以上可知,最初的问题 int person_age(person_t* self)
中的标识符 person_t
尝试从通常命名空间中查找, 而这个标识符在标签名命名空间,因此编译器提示: 找不到标识符的错误。
将声明代码修改如下, 上面代码的两个分支都可以正常编译(C/C++):
typedef struct person_t { // 这个 person_t 定义在标签名命名空间
unsigned char* name;
int age;
} person_t, *pperson_t; // 这个 person_t 与 pperson_t 定义在通常命名空间
而到了这里,也解释了我很久以前的另一个疑惑,为什么会出现很多这样的结构体声明:
typedef struct _WIN32_FILE_ATTRIBUTE_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
} WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA;
Comments ()