标准 C 现在是 ISO/IEC 9989:2011
2011 C 标准于 2011 年 12 月 19 日星期一由 ISO 发布(或者更准确地说,它已发布的通知已于 19 日添加到委员会网站;该标准可能已发布为'很久以前'作为2011-12-08)。请参阅WG14 网站上的公告。可悲的是,PDF from ISO 的价格为 338 瑞士法郎,而ANSI 的价格为 387 美元。
- 您可以花 30 美元从ANSI 获取 INCITS/ISO/IEC 9899:2012 (C2011) 的 PDF。
- 您可以以 30 美元的价格从ANSI 获取 INCITS/ISO/IEC 14882:2012 (C++2011) 的 PDF。
主要答案
问题是“C 中是否允许重复的 typedefs”?答案是“不——不在 ISO/IEC 9899:1999 或 9899:1990 标准中”。原因可能是历史原因;最初的 C 编译器不允许这样做,因此最初的标准化者(他们被授权对 C 编译器中已有的内容进行标准化)标准化了这种行为。
请参阅 Als 的 answer,了解 C99 标准禁止重复类型定义的位置。 C11 标准已将 §6.7 ¶3 中的规则更改为:
3 如果标识符没有链接,则标识符的声明不得超过一个
(在声明符或类型说明符中)具有相同的范围和相同的名称空间,除了
那:
- 可以重新定义 typedef 名称以表示与当前相同的类型,
前提是该类型不是可变修改的类型;
- 标签可以按照 6.7.2.3 中的规定重新声明。
所以现在在 C11 中有一个明确的要求重复 typedef。继续推出兼容 C11 的 C 编译器。
对于那些仍在使用 C99 或更早版本的人,接下来的问题大概是“那么我如何避免遇到重复 typedef 的问题?”
如果您遵循这样的规则,即有一个头文件定义了多个源文件中所需的每种类型(但可以有许多头文件定义此类类型;但每种单独的类型只能在一个头文件中找到) ,并且如果在需要该类型的任何时候都使用该标头,那么您就不会遇到冲突。
如果您只需要指向类型的指针并且不需要分配实际结构或访问它们的成员(不透明类型),您也可以使用不完整的结构声明。同样,设置关于哪个标头声明不完整类型的规则,并在需要该类型的任何地方使用该标头。
另见What are extern variables in C;它谈论变量,但类型可以类似地对待。
评论中的问题
我非常需要“不完整的结构声明”,因为单独的预处理器复杂性会禁止某些包含。所以你的意思是,如果它们被完整的标题再次类型定义,我不能对这些前向声明进行类型定义?
或多或少。我真的不需要处理这个问题(尽管工作中的系统的某些部分非常接近不得不担心它),所以这有点试探性,但我相信它应该可以工作。
通常,标头详细描述了“库”(一个或多个源文件)提供的外部服务,以便库的用户能够使用它进行编译。尤其是在有多个源文件的情况下,还可能有一个内部头来定义,例如,完整的类型。
所有标头都是 (a) 自包含的和 (b) 幂等的。这意味着您可以 (a) 包含标头并自动包含所有必需的其他标头,并且 (b) 您可以多次包含标头而不会引起编译器的愤怒。后者通常通过标头保护来实现,尽管有些人更喜欢#pragma once - 但那是不可移植的。
所以,你可以有一个这样的公共标头:
public.h
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
#include <stddef.h> // size_t
typedef struct mine mine;
typedef struct that that;
extern size_t polymath(const mine *x, const that *y, int z);
#endif /* PUBLIC_H_INCLUDED */
到目前为止,争议不大(尽管人们可以合理地怀疑这个库提供的接口非常不完整)。
private.h
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include "public.h" // Get forward definitions for mine and that types
struct mine { ... };
struct that { ... };
extern mine *m_constructor(int i);
...
#endif /* PRIVATE_H_INCLUDED */
同样,争议不大。 public.h 标头必须首先列出;这提供了自我封闭的自动检查。
消费者代码
任何需要polymath() 服务的代码都写:
#include "public.h"
这就是使用该服务所需的所有信息。
提供者代码
库中定义polymath() 服务的任何代码都会写入:
#include "private.h"
此后,一切正常。
其他供应商代码
如果有另一个库(称为multimath())使用polymath() 服务,那么该代码将像任何其他消费者一样包含public.h。如果polymath() 服务是multimath() 的外部接口的一部分,那么multimath.h 公共标头将包含public.h(抱歉,我在接近尾声时切换了术语,这里)。如果multimath() 服务完全隐藏了polymath() 服务,那么multimath.h 标头将不包含public.h,但multimath() 私有标头可能会这样做,或者需要@987654349 的单个源文件@services 可以在需要时包含它。
只要您认真遵守在任何地方都包含正确标题的纪律,那么您就不会遇到双重定义的麻烦。
如果您随后发现其中一个标头包含两组定义,一组可以在没有冲突的情况下使用,另一组有时(或总是)与某些新标头(以及其中声明的服务)冲突,那么您需要将原始标题拆分为两个子标题。每个子标题单独遵循此处详述的规则。原始标题变得微不足道 - 标题保护和包含两个单独文件的行。所有现有的工作代码都保持不变 - 尽管依赖项发生了变化(要依赖的额外文件)。新代码现在可以包含相关的可接受的子标头,同时还可以使用与原始标头冲突的新标头。
当然,您可以有两个根本不可调和的标题。举一个人为的例子,如果有一个(设计糟糕的)标头声明了 FILE 结构的不同版本(与 <stdio.h> 中的版本不同),那么您将被水洗;代码可以包含设计不良的标头或<stdio.h>,但不能同时包含两者。在这种情况下,应该修改设计不佳的标头以使用新名称(可能是File,但也可能是其他名称)。如果您必须在公司收购后将两个产品的代码合并为一个,并使用一些常见的数据结构,例如用于数据库连接的DB_Connection,您可能会更实际地遇到这个麻烦。在没有 C++ namespace 功能的情况下,您会被困在对一组或多组代码进行重命名练习。