关于C与C++的头文件使用区别与规定
标准C++ C库子集头策略 文章来源
Copyright 1997,1998 by Nathan C. Myers All Rights Reserved.
Abstract
摘要:本报告是为标准C++实现者准备的。
这是一份针对标准C++实现者的报告。它描述了当(由于任何原因)必须使用一组底层C头文件来提供大部分必要的声明时,产生C++的C子集所需的头文件的方法。
C++标准对C库的内容和从C++程序中看到的C头文件提出了不同于C标准的要求。在底层C头文件必须按原样使用、不改变或不重复的外部约束下,这些要求可能会很棘手,而在程序必须保留对这些头文件中定义的非标准名称的访问的额外约束下,这些要求就更难满足了。本文描述了一种架构,它能满足草案的所有要求,并将重复和无关的设备降到最低。它的目的是为了那些无论出于什么原因,都无法在C++中直接实现C库子集的人。
这种方法依赖于一些关于标准和非标准文件集的 “内部信息”,这些文件可以被标准的 "#include <>"指令所包含。这意味着,独立于底层C头文件的完全通用的C++头文件是不可能的;然而,这种依赖性远远小于内容重复的情况,所有需要的信息都可以通过对目标C头文件的检查来获得,无论是手动还是通过脚本。
在任何解决这个问题的方法中,一个关键的复杂问题来自于Koenig Lookup效应。简而言之,一个在另一个命名空间中定义的类型的使用声明并不等同于一个实际的类型定义。考虑一下
namespace A { // some namespace
struct U {};
}
namespace std {
struct T {};
void f(const T&);
using A::U; // or typedef A::U U;
void g(const U&);
}
int main() {
f(std::T()); // OK
g(std::U()); // error
}
名字’g’只在命名空间A和全局作用域中被查找。这意味着任何指定出现在命名空间 std 中的结构定义,以及任何依赖于它们的函数,都必须实际出现在 std 命名空间中,而不是简单地被别名在那里。
通过www.DeepL.com/Translator(免费版)翻译
所需经费
以下是为所提出的解决方案的设计所假设的要求清单。
-
一个符合要求的实现必须提供所有的头文件<NAME.h>和,其中NAME是每个标准的C语言头文件。
-
当包含<NAME.h>时,其中定义的标准名称必须在全局命名空间和命名空间std中都可用。
-
当包含(only)时,标准名称必须只出现在命名空间std中,而不能出现在全局命名空间中,任何非标准扩展都不应出现在全局或std命名空间中。
-
无论是否包含、<NAME.h>或两者都包含,用户必须能够在全局范围内对<cNAME.h>中的某些FUNC或两者都包含的FUNC写 "使用std::FUNC; "或 “使用命名空间std;”,而不会产生冲突或歧义。
-
当包含<NAME.h>时,::FUNC和std::FUNC,以及::TYPE和std::TYPE(对于标准函数FUNC和标准类型TYPE)必须指向同一个对象–实际上是同一个类型或函数,而不仅仅是等价物。(外部 "C "版本的FUNC在全局范围内链接,其链接名在全局范围内,可能与这两者都不一样。)
-
序列
cpp #include <cName> using namespace std;
必须在全局范围内只暴露标准名称,但是 #include <name.h>应该公开标准名称和任何非标准、专有 的扩展名。
应该公开标准名称和任何非标准的专有扩展名。 -
C++规定了允许出现在头文件中的宏的完整列表,C头文件提供的其他宏必须是undef’d,用常量定义或内联函数代替。其他由C头文件提供的宏必须是undef’d,并用常量定义或内联函数代替。任何定义为宏的非标准名称都应该被删除,或者至少用#ifdefs来保护。特别是像 "putchar "这样的常用宏必须用作用域函数代替,可能是内联函数。
以下几点可能需要一些思考才能完全理解。 -
在命名空间中定义的类型会通过Koenig查找引起与该命名空间的交互,所以标准类型结构必须真正在std中定义,而不仅仅是在那里别名。
-
C++中的一些原型,如strchr和qsort,与C的对应函数不同,所以简单化的方法是不够的;特别是重载禁止将它们定义为外部 "C "函数,因为C标准指定的函数有错误的类型。
-
程序将与C库进行链接,其外部 "C "名称处于 “链接空间”,在实现C++库时可以访问使用,但被C++代码隐藏起来,无法访问。由于没有符合要求的程序可以声明这些全局名,所以没有程序会用这个名字访问它们。
换句话说,不需要允许 “#include <name.h>”,也不需要允许全局外部的 "C "从头声明一个名字。一些C++实现的C函数必须替换C声明,在声明空间,但不在 "extern “C”"链接空间。
通过www.DeepL.com/Translator(免费版)翻译
建筑学
假设在一个已知的目录中已经有一组C语言头文件<NAME.h>。这些头文件是 "C++干净 "的,因为它们符合核心ANSI C和C++语言语法规范的通用子集。(也就是说,它们不是 "K&R "的C语言声明。)这是整个行业的普遍现状(虽然不是普遍的)。
C++实现必须提供自己的一个或多个目录来代替或至少在C头目录之前进行搜索。对于标准C库中发现的每一个<NAME.h>形式,它必须在这个目录中放置两个 "影子 "头文件。"NAME.h "和 “cNAME”,它们又包括实际的C头文件。此外,它还必须为任何一个C头文件所包含的每个非标准文件(顺便)提供一个影子头<NAME.h>。(这些非标准头可以通过一个配置脚本来识别和生成。这样的脚本已经写好了)。)
这些影子头提供了一个地方来纠正C库名的C和C++定义之间的差异。修正的内容包括未定义C宏,在命名空间中封装声明,将名称提升到全局范围,以及针对特殊情况的各种令人震惊的宏手术,例如va_start、strchr、qsort和FILE。
为了在命名空间中嵌套声明,而不使子包含的声明进一步嵌套在子命名空间中,需要一个明显但严格有限的预处理器装置。在C头文件中的``extern “C”’'块中不加限制地使用子包含会干扰这一点。
典型的头文件是这样的。
/* inherited C header foo.h, in C header directory */
#ifndef _INCLUDED_FOO_ /* ordinary include guard */
#define _INCLUDED_FOO_
extern int foo_this(const char*); /* ordinary C */
extern int foo_that(const char*);
#endif /* _INCLUDED_FOO_ */
// C++ header <cfoo>
#ifndef _INCLUDED_CPP_CFOO_ /* ordinary include guard */
#define _INCLUDED_CPP_CFOO_
namespace _C_Swamp {
extern "C" {
# define _IN_C_SWAMP_
# include "/usr/include/foo.h" /* or #include_next <foo.h> */
}
namespace _C_Shadow { } // placeholder
} // close namespace ::_C_Swamp::
# undef foo_this
# undef foo_that
namespace std {
// Adopt C names into std::
using ::_C_Swamp::foo_this;
using ::_C_Swamp::foo_that;
// ... and others
} // close namespace std::
#undef _IN_C_SWAMP_
#endif /* _INCLUDED_CPP_CFOO_ */
// C++ "shadow" header <foo.h>
#ifndef _INCLUDED_CPP_FOO_H_
# undef _SHADOW_NAME
# define _SHADOW_NAME <cfoo> /* substitute here */
# include <generic_shadow.h>
# undef _SHADOW_NAME
#ifndef _IN_C_SWAMP_
using std::foo_this;
using std::foo_that;
#define _INCLUDED_CPP_FOO_H_
#endif
#endif /* _INCLUDED_CPP_FOO_H_ */
其中用于所有标准".h "头的文件<generic_shadow.h>是这样的。
// <generic_shadow.h>
#ifdef _IN_C_SWAMP_ /* sub-included by a C header */
// get out of the "swamp"
} // close extern "C"
} // close namespace _C_Swamp::
# undef _IN_C_SWAMP_
# include _SHADOW_NAME
// dive back into the "swamp"
namespace _C_Swamp_ {
extern "C" {
# define _IN_C_SWAMP_
#else /* not _IN_C_SWAMP_: directly included by user program */
# include _SHADOW_NAME
// expose global C names, including non-standard ones, but shadow
// some names and types with the std:: C++ version.
using namespace ::_C_Swamp::_C_Shadow;
#endif /* _IN_C_SWAMP_ */
最后,由标准头文件所包含的非标准头文件需要被影子化,模式是这样的,常见的例子是sys/types.h。
// shadow header sys/types.h
#ifndef _INCLUDED_CPP_SYS_TYPES_H_
# ifdef _IN_C_SWAMP_ /* sub-included by a C header */
# include </usr/include/sys/types.h>
# else
namespace _C_Swamp { namespace _C_Shadow { } }
using namespace ::_C_Swamp::_C_Shadow;
namespace _C_Swamp_ {
extern "C" {
# define _IN_C_SWAMP_
# include </usr/include/sys/types.h>
} // close extern "C"
} // close namespace _C_Swamp::
# endif /* _IN_C_SWAMP_ */
#endif /* _INCLUDED_CPP_SYS_TYPES_H_ */
要制作和维护需要这种处理的文件列表可能并不容易;#如果指令干扰了自动化的过程。然而,你可以通过为任何可能被标准头包含的文件提供一个影子来保守。我有一个shellscript可以生成这样一个列表,可以根据要求提供。如果你能安排好只有通过影子才能找到文件,你至少可以可靠地检测出你是否遗漏了一个文件。
通过www.DeepL.com/Translator(免费版)翻译