关于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(免费版)翻译

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C、传统 C++ #include <assert.h>    //设定插入点 #include <ctype.h>     //字符处理 #include <errno.h>     //定义错误码 #include <float.h>     //浮点数处理 #include <fstream.h>    //文件输入/输出 #include <iomanip.h>    //参数化输入/输出 #include <iostream.h>   //数据流输入/输出 #include <limits.h>    //定义各种数据类型最值常量 #include <locale.h>    //定义本地化函数 #include <math.h>     //定义数学函数 #include <stdio.h>     //定义输入/输出函数 #include <stdlib.h>    //定义杂项函数及内存分配函数 #include <string.h>    //字符串处理 #include <strstrea.h>   //基于数组的输入/输出 #include <time.h>     //定义关于时间的函数 #include <wchar.h>     //宽字符处理及输入/输出 #include <wctype.h>    //宽字符分类 ////////////////////////////////////////////////////////////////////////// 标准 C++ (同上的不再注释) #include <algorithm>    //STL 通用算法 #include <bitset>     //STL 位集容器 #include <cctype> #include <cerrno> #include <clocale> #include <cmath> #include <complex>     //复数类 #include <cstdio> #include <cstdlib> #include <cstring> #include <ctime> #include <deque>      //STL 双端队列容器 #include <exception>    //异常处理类 #include <fstream> #include <functional>   //STL 定义运算函数(代替运算符) #include <limits> #include <list>      //STL 线性列表容器 #include <map>       //STL 映射容器 #include <iomanip> #include <ios>       //基本输入/输出支持 #include <iosfwd>     //输入/输出系统使用的前置声明 #include <iostream> #include <istream>     //基本输入流 #include <ostream>     //基本输出流 #include <queue>      //STL 队列容器 #include <set>       //STL 集合容器 #include <sstream>     //基于字符串的流 #include <stack>      //STL 堆栈容器     #include <stdexcept>    //标准异常类 #include <streambuf>    //底层输入/输出支持 #include <string>     //字符串类 #include <utility>     //STL 通用模板类 #include <vector>     //STL 动态数组容器 #include <cwchar> #include <cwctype> using namespace std; ////////////////////////////////////////////////////////////////////////// C99 增加 #include <complex.h>   //复数处理 #include <fenv.h>    //浮点环境 #include <inttypes.h>  //整数格式转换 #include <stdbool.h>   //布尔环境 #include <stdint.h>   //整型环境 #include <tgmath.h>   //通用类型数学宏 --------------------------------------------------------------------------------------------------------- 补充: 经常在CSDN以及其他之类的技术论坛上问关于C++ 头文件的问题。提出这些问题的往往就是那些刚学C++的新手。当初我是菜鸟的时候也问过类似的问题。 现在来看看下面两个include: #include<iostream> // 这个就是1998年标准化以后的标准头文件 #include<iostream.h> // 这个就是标准化以前的头文件 更本质上的区别就是iostream把标准C++库的组件放在一个名位std的namespace里面。而相对的iostream.h则将这些标准组件放在全局空间里,同时在标准化以后旧有的C标准库也已经经过改造了。 看看下面这两个头文件 // 标准化后经过改造的C的标准库,所有的组件都放在了std中 #include<cstdio> // 标准化以前C++中的C标准库 #include<stdio.h> // 在看看这个头文件C标准库下 基于char* 的字符处理函数库 #include<string.h> // 在标准化以后他变成了这样 #include<cstring> // 但是很多朋友还看见过这个字符串处理函数库,他包含了新的string class #include<string> 经过了标准委员会如此大规模手术后,在98年以前出品的C++编译器(BC3.0,BC5.0)上能顺利通过编译的源文件,在支持新标准的编译器上可能无法顺利通过编译也就是很正常的事了。 [起因] 在回过头来看看标准程序库,这个程序库涵盖范围相当广大,提过了许许多多好用的功能。正是因为这样标准程序库中class的名称和函数名与第三方提供的程序库中的class名或是函数名发生名字冲突的可能性大大增大。为了避免这个问题的发生,标准委员会决定将标准程序库中每一样东西都放在namespace std中。但是这么做同时有引来了一个新的问题。很多C++程序代码依赖那些已经存在很多年的C++ “准”标准程序库(C++迟迟未标准化才导致这些情况的发生),例如iosteam.h,complex.h等等。 为了解决这个新出现的问题,标准化委员会决定设计一些新的头文件名,给那些穿上std外衣的组件所使用。把C++头文件的.h去掉,于是就有前面出现的iostream,同样C的头文件也做了相同的处理,同时在前面加上了一个字母c,以表示是C的头文件(感觉上有中种族歧视的感觉)。同时标准化委员会声明就有的C++头文件将不再列于被支持的名单之中了,而旧有的C头文件为了满足“对C的兼容性”这个古老契约,仍然将继续存活下去。 但是,那些编译器厂商不可能去推翻他们客户的旧有编译器(也跟本不会去这么做),所以那些旧有的C++头文件仍然苟延残喘的活了下来,并不断的扰乱那些C++新兵的心智。 下面就是现在大多数C++开发工具表示头文件的组织状态: 1. 旧的C++头文件 比如iostream.h,他们虽然被标准化委员会所抛弃,但由于各大厂商为了各自的商业利益仍然将继续存活下去,这些头文件的内容将不处于namespace std中。 2. 新的C++头文件如iostream虽然提供了和旧有头文件相同的功能,但他的内容都并入了namespace std中,从而有效避免了名字污染的问题。 3. 标准C的头文件如stdio.h继续获得支持,这类文件的内容并未放在std中。 4. C函数库的技能也有对应的新式C++版本,起名称类似cstdio,这类头文件的内容也有幸穿上了std的外衣。 其实标准化以后的标准程序库的改动并不只有这些而已,很多的标准化组件都被“tamplate化”。其中就有元老级人物iostream。标准程序库的问题并不是用一篇,两篇文章就可以说清楚的。如果你像进一步的了解C++的标准程序库的话,你可以看看侯先生的《C++标准程序库》。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值