Рассмотрим следующий (глючный) код 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
(имея в виду ::abs
) вместо std::abs
, В зависимости от реализации, ::abs
может не существовать, или это может быть C abs
или это может быть набор перегрузки, включая версию для double
, лайк std::abs
является.
С Clang в Linux, по крайней мере, в моей среде, это второй вариант: 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 я получаю разные результаты в разных средах, и я еще не выяснил, какие детали среды актуальны. Однако более распространенным вариантом является также то, что он вызывает C abs
функция. Однако даже при -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
такое перегрузка?
Вот решение. Я не доволен этим, но это может сработать для вас
namespace DontUseGlobalNameSpace {
// put all std functions here you want to catch
int abs(int x);
}
using namespace DontUseGlobalNameSpace;
Теперь, если вы используете abs()
без уточнения вы получите ошибку «символ неоднозначен».
Это будет сложно. 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 abs
в <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++98
в g++
командной строки, чтобы подчеркнуть, что это просто старая школа C ++ namespace
семантика на работе.
Этот код позволит вам определить, существует ли ловушка в конкретной среде:
double (*)(double) = &::abs; // fails if you haven't included math.h, possibly via cmath
Но это не поможет вам определить места, в которые вы попали в ловушку.