14

考虑以下(错误的)C++ 代码:

#include <cmath>
#include <cstdlib>

#include <iostream>

int main() {
    if (abs(-0.75) != 0.75) {
        std::cout << "Math is broken!\n";
        return 1;
    } else {
        return 0;
    }
}

这段代码有问题,因为它调用abs(meaning ::abs) 而不是std::abs. 根据实现,::abs可能不存在,或者它可能是 C abs,或者它可能是一个重载集,包括一个版本double,就像std::absis。

在 Linux 上使用 Clang,至少在我的环境中,它是第二种选择: C abs。这会引发两个警告,即使没有明确启用任何警告:

<source>:7:9: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value]
    if (abs(-0.75) != 0.75) {
        ^
<source>:7:9: note: use function 'std::abs' instead
    if (abs(-0.75) != 0.75) {
        ^~~
        std::abs
<source>:7:13: warning: implicit conversion from 'double' to 'int' changes value from -0.75 to 0 [-Wliteral-conversion]
    if (abs(-0.75) != 0.75) {
        ~~~ ^~~~~

在 GCC 上,我在不同的环境中得到不同的结果,我还没有弄清楚环境的哪些细节是相关的。不过,更常见的选项是它调用 Cabs函数。但是,即使使用-Wall -Wextra -pedantic,它也不会发出警告。我可以用 强制发出警告-Wfloat-conversion,但这会给我的代码库的其余部分带来太多误报(也许我应该修复,但这是一个不同的问题):

<source>: In function 'int main()':
<source>:7:18: warning: conversion to 'int' alters 'double' constant value [-Wfloat-conversion]
     if (abs(-0.75) != 0.75) {
                  ^

std每当我通过全局命名空间使用库函数时,当命名空间中的版本是重载时,有没有办法获得警告?

4

3 回答 3

3

这是一个解决方案。我对此不满意,但它可能对您有用:

namespace DontUseGlobalNameSpace {
// put all std functions here you want to catch
int abs(int x);
}
using namespace DontUseGlobalNameSpace;

现在,如果你abs()不加限定地使用,你会得到一个“符号不明确”的错误。

于 2017-07-13T18:59:19.427 回答
2

这将是困难的。GCC<cmath>标头仅包含<math.h>#undefs其宏(以防万一)并将 C++ 函数定义为内联函数,这些函数使用<math.h>. 大多数函数实际上是指编译器内置函数:例如,std::abs是使用__builtin_abs而不是定义的::abs

由于<cmath>和您的“错误程序”都在同一个翻译单元中,因此很难看出如何分离可见性:如何<cmath>允许内联函数使用<math.h>东西,而您的代码不会。

嗯,有以下方法:<cmath>必须重写以提供它自己的本地范围声明,用于它需要的任何内容,<math.h>而不是实际包含该标头。

我们可以做的是准备一个头文件,它重新声明我们不想要的函数,并带有__attribute__ ((deprecated))

// put the following and lots of others like it in a header:
extern "C" int abs(int) throw () __attribute__ ((deprecated));
#include <cmath>
#include <cstdlib>

#include <iostream>

int main() {
  if (abs(-0.75) != 0.75) {
    std::cout << "Math is broken!\n";
    return 1;
  } else {
    return 0;
  }
}

现在:

$ g++ -Wall  buggy.cc
buggy.cc: In function ‘int main()’:
buggy.cc:9:7: warning: ‘int abs(int)’ is deprecated [-Wdeprecated-declarations]
   if (abs(-0.75) != 0.75) {
       ^~~
In file included from /usr/include/c++/6/cstdlib:75:0,
                 from buggy.cc:4:
/usr/include/stdlib.h:735:12: note: declared here
 extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;
            ^~~
buggy.cc:9:16: warning: ‘int abs(int)’ is deprecated [-Wdeprecated-declarations]
   if (abs(-0.75) != 0.75) {
                ^
In file included from /usr/include/c++/6/cstdlib:75:0,
                 from buggy.cc:4:
/usr/include/stdlib.h:735:12: note: declared here
 extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;
            ^~~

链接器警告会更简单。我试过了;问题是这个测试程序实际上并没有生成一个外部引用abs(即使有一个#undef absin <cmath>)。该调用被内联,因此避开了链接器警告。

更新:

跟进 DanielH 的评论,我想出了一个允许std::abs但阻止的技巧的改进abs

#include <cmath>
#include <cstdlib>
#include <iostream>

namespace proj {
  // shadowing declaration
  int abs(int) __attribute__ ((deprecated));

  int fun() {
    if (abs(-0.75) != 0.75) {
      std::cout << "Math is broken!\n";
      return 1;
    } else {
      return std::abs(-1); // must be allowed
    }
  }
}

int main() {
  return proj::fun();
}

可以使用简单的命名空间。另外,我们不需要deprecated属性;我们可以只声明abs一个不兼容的函数,或者完全声明一个非函数标识符:

#include <cmath>
#include <cstdlib>
#include <iostream>

namespace proj {
  // shadowing declaration
  class abs;

  int fun() {
    if (abs(-0.75) != 0.75) {
      std::cout << "Math is broken!\n";
      return 1;
    } else {
      return std::abs(-1); // must be allowed
    }
  }
}

int main() {
  return proj::fun();
}

$ g++ -std=c++98 -Wall  buggy.cc -o buggy
buggy.cc: In function ‘int proj::fun()’:
buggy.cc:10:18: error: invalid use of incomplete type ‘class proj::abs’
     if (abs(-0.75) != 0.75) {
                  ^
buggy.cc:7:9: note: forward declaration of ‘class proj::abs’
   class abs;
         ^~~
buggy.cc:16:3: warning: control reaches end of non-void function [-Wreturn-type]
   }
   ^

使用这种方法,我们只需要一个名称列表并将它们转储到一些提供此功能的标题中:

int abs, fabs, ...; // shadow all of these as non-functions

-stdc++98g++命令行中使用来强调这只是在工作的老式 C++namespace语义。

于 2017-07-13T18:35:41.083 回答
0

此代码将让您检测陷阱是否存在于特定环境中:

double (*)(double) = &::abs; // fails if you haven't included math.h, possibly via cmath

但它不会帮助你发现你落入陷阱的地方。

于 2017-07-13T18:29:05.757 回答