问题
Consider the following (buggy) C++ code:
#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;
}
}
This code is buggy because it calls abs
(meaning ::abs
) instead of std::abs
. Depending on the implementation, ::abs
might not exist, or it might be the C abs
, or it might be an overload set including a version for double
, like std::abs
is.
With Clang on Linux, at least in my environment, it turns out to be the second option: C abs
. This provokes two warnings, even without explicitly enabling any:
<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) {
~~~ ^~~~~
On GCC, I get different results in different environments and I haven’t yet figured out what details of the environment are relevant. The more common option, though, is also that it calls the C abs
function. However, even with -Wall -Wextra -pedantic
, it gives no warnings. I can force a warning with -Wfloat-conversion
, but that gives too many false positives on the rest of my codebase (which perhaps I should fix, but that’s a different issue):
<source>: In function 'int main()':
<source>:7:18: warning: conversion to 'int' alters 'double' constant value [-Wfloat-conversion]
if (abs(-0.75) != 0.75) {
^
Is there a way to get a warning whenever I use a library function through the global namespace, when the version in namespace std
is an overload?
回答1:
Here's a solution. I'm not happy with it, but it might work for you:
namespace DontUseGlobalNameSpace {
// put all std functions here you want to catch
int abs(int x);
}
using namespace DontUseGlobalNameSpace;
Now, if you use abs()
without qualification, you'll get a "symbol is ambiguous" error.
回答2:
This is going to be difficult. The GCC <cmath>
header simply includes <math.h>
, #undefs
its macros (just in case) and defines the C++ functions as inline functions which make some use of identifiers from <math.h>
. Most of the functions in fact refer to compiler builtins: for instance, std::abs
is defined using __builtin_abs
and not ::abs
.
Since <cmath>
and your "buggy program" are all in the same translation unit, it's hard to see how the visibility could be separated: how the inline functions in <cmath>
could be allowed to use <math.h>
stuff, while your code wouldn't.
Well, there is the following way: <cmath>
would have to be rewritten to provide its own locally scoped declarations for anything that it needs from <math.h>
and not actually include that header.
What we can do instead is prepare a header file which re-declares the functions we don't want, with an __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;
}
}
Now:
$ 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;
^~~
A linker warning would be simpler. I tried that; the problem is that this test program doesn't actually generate an external reference to abs
(even though there is an #undef abs
in <cmath>
). The call is being inlined, and so evades the linker warning.
Update:
Following up DanielH's comment, I have come up with a refinement of the trick which allows std::abs
but blocks 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();
}
Simple namespaces can be used. Also, we don't need the deprecated
attribute; we can just declare abs
as an incompatible function, or a non-function identifier entirely:
#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]
}
^
With this approach, we just need a list of names and dump them into some header that provides this:
int abs, fabs, ...; // shadow all of these as non-functions
I used -stdc++98
in the g++
command line to emphasizes that this is just old school C++ namespace
semantics at work.
回答3:
This code will let you detect whether the trap exists in a particular environment:
double (*)(double) = &::abs; // fails if you haven't included math.h, possibly via cmath
But it won't help you spot the places you fall into the trap.
来源:https://stackoverflow.com/questions/45087872/is-there-a-gcc-warning-for-using-symbols-from-the-c-library-not-through-namespac