Есть ли предупреждение GCC для использования символов из библиотеки C не через пространство имен std?

Рассмотрим следующий (багги) код С++:

#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 on 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 является перегрузкой?

Ответы

Ответ 1

Вот решение. Я не доволен этим, но это может сработать для вас:

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

Теперь, если вы используете abs() без квалификации, вы получите ошибку "символ является неоднозначной".

Ответ 2

Это будет сложно. Заголовок GCC <cmath> просто включает <math.h>, #undefs его макросы (на всякий случай) и определяет функции С++ как встроенные функции, которые используют некоторые идентификаторы из <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 in <cmath>). Вызов встраивается и поэтому уклоняется от предупреждения компоновщика.

Update:

Следуя комментарию 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++, чтобы подчеркнуть, что это просто старая школьная С++ namespace семантика на работе.

Ответ 3

Этот код позволит вам определить, существует ли ловушка в конкретной среде:

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

Но это не поможет вам определить места, в которые вы попадаете в ловушку.