【持续更新】C 和 C++ 区别很大!

lyazj / 2023-07-23 / 原文

一些容易被忽略的 C 与 C++ 的重要区别

头文件和命名空间

C 标准库头文件名在 C++ 中通常去除扩展名,并加上 c 前缀,如:

  • stdio.h -> cstdio
  • stdlib.h -> cstdlib

其中一个重要的区别是后者保证与 C 库兼容的各个函数名可以在 std 命名空间中找到,但并不保证它们不存在于根命名空间中,这可能会引发一些难以发现的 bug。

/*
 * abs.cpp - 求绝对值的小程序
 *
 * 该代码有一处较为隐蔽的 bug,请尽力找出它!
 */
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cerrno>

int main(int argc, char *argv[])
{
  if(argc == 1) {  // 校验输入命令行参数
    fprintf(stderr,
        "usage: %s <numbers>\n", program_invocation_short_name);
    exit(EXIT_FAILURE);
  }

  for(int i = 1; i < argc; ++i) {  // 对每个参数,输出运算结果
    char *endp;
    double num = strtod(argv[i], &endp);
    if(*endp) {  // 没有读到字符串结尾,说明出现了错误
      fprintf(stderr, "ERROR: invalid number: %s", argv[i]);
    }
    num = abs(num);  // 计算绝对值
    printf("%lf\n", num);
  }

  return 0;  // 此处始终退回成功值,不是 bug
}

不幸地是,上面的代码顺利通过了编译,且 GCC 发出没有任何警告。

结合输出结果便更容易发现这个 bug!

lyazj@HelloWorld:~$ g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

lyazj@HelloWorld:~$ g++ -Wall -Wshadow -Wextra abs.cpp -o abs
lyazj@HelloWorld:~$ ./abs
usage: abs <numbers>
lyazj@HelloWorld:~$ ./abs 0 1 -1 1.5 -1.5
0.000000
1.000000
1.000000
1.000000
1.000000

无论你是用人脑,IDE,反汇编器还是调试器发现了这个 bug,你都会将矛头指向命名空间。其中一种解决方案是简洁明了的,即在 main() 函数定义前加上一句:

using std::abs;

现在一切正常。

lyazj@HelloWorld:~$ g++ -Wall -Wshadow -Wextra abs.cpp -o abs
lyazj@HelloWorld:~$ ./abs 0 1 -1 1.5 -1.5
0.000000
1.000000
1.000000
1.500000
1.500000

另一种解决方案是不加 using 申明,但将头文件名修改为 C 风格:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>

结果仍然一切正常。

lyazj@HelloWorld:~$ g++ -Wall -Wshadow -Wextra abs.cpp -o abs
lyazj@HelloWorld:~$ ./abs 0 1 -1 1.5 -1.5
0.000000
1.000000
1.000000
1.500000
1.500000

现在让我们来做最后的测试,不改变上一步的代码,改用 gcc 编译,结果如下:

lyazj@HelloWorld:~$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

lyazj@HelloWorld:~$ gcc -xc -Wall -Wshadow -Wextra abs.cpp -o abs -D_GNU_SOURCE
abs.cpp: In function ‘main’:
abs.cpp:25:11: warning: using integer absolute value function ‘abs’ when argument is of floating-point type ‘double’ [-Wabsolute-value]
   25 |     num = abs(num);  // 计算绝对值
      |           ^~~
lyazj@HelloWorld:~$ ./abs 0 1 -1 1.5 -1.5
0.000000
1.000000
1.000000
1.000000
1.000000

gcc 发出了我们所期待的警告,这是在本节开头 g++ 没有做到的事情。

问题补充:

  • 应当避免随意使用 using namespace std;,这并非解决这类冲突的好办法
  • 使用 C 库函数时,遵循 C 风格,使用后缀区别不同类型的 abs() 函数
  • 使用 IDE 或调试器追踪检查函数重载解析结果,确保符合预期

对于 C 和 C++ 风格头文件处理命名空间行为不一致的问题,可以尝试:

  • 不要混用两种风格的头文件
  • 如使用 C++ 风格的头文件,请坚守命名空间规则,确保使用的函数重载在当前作用域可见
  • 如使用 C 风格的头文件,请以兼容 C 的方式编写代码,包括以后缀区分接收不同形参类型的函数