Kann eine Deklaration den Standard-Namespace beeinflussen?

96
#include <iostream>
#include <cmath>

/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
    return a > 0? -a : a;
}

int main() {
    int a = abs(-5);
    int b = std::abs(-5);
    std::cout<< a << std::endl << b << std::endl;
    return 0;
}

Ich habe erwartet, dass die Ausgabe -5und sein wird 5, aber die Ausgabe ist die -5und -5.

Ich frage mich, warum dieser Fall passieren wird.

Hat es etwas mit der Verwendung von stdoder was zu tun ?

Peter
quelle
1
Ihre Implementierung von absist falsch.
Richard Critten
31
@ RichardCritten Das ist der Punkt. OPs fragen, warum das Hinzufügen dieser defekten absEffekte std::abs().
HolyBlackCat
11
Interessant, ich bekomme 5und 5mit Klirren -5und -5mit gcc.
Rakete1111
10
Cmake ist kein Compiler, sondern ein Build-System. Sie können cmake verwenden, um mit verschiedenen Compilern zu erstellen.
HolyBlackCat
5
Ich hätte Ihnen wahrscheinlich empfohlen, einfach Ihre Funktion zu haben return 0- das hätte vermieden, dass die Leute glauben, Sie hätten die Funktion unbeabsichtigt falsch implementiert und das gewünschte und tatsächliche Verhalten klarer gemacht.
Bernhard Barker

Antworten:

92

Die Sprachspezifikation ermöglicht die Implementierung von Implementierungen, <cmath>indem die Standardfunktionen im globalen Namespace deklariert (und definiert) und dann stdmithilfe von using-Deklarationen in den Namespace gebracht werden. Es ist nicht spezifiziert, ob dieser Ansatz verwendet wird

20.5.1.2 Header
4 [...] In der C ++ - Standardbibliothek liegen die Deklarationen (mit Ausnahme von Namen, die in C als Makros definiert sind) jedoch im Namespace-Bereich (6.3.6) des Namespace std. Es ist nicht spezifiziert, ob diese Namen (einschließlich aller in den Abschnitten 21 bis 33 und Anhang D hinzugefügten Überladungen) zuerst im globalen Namespace-Bereich deklariert und dann stddurch explizite using-Deklarationen (10.3.3) in den Namespace eingefügt werden.

Anscheinend haben Sie es mit einer der Implementierungen zu tun, die sich für diesen Ansatz entschieden haben (z. B. GCC). Dh Ihre Implementierung bietet ::abs, während std::abseinfach "bezieht" ::abs.

Eine Frage, die in diesem Fall noch offen ist, ist, warum Sie zusätzlich zu dem Standard ::absIhren eigenen deklarieren konnten ::abs, dh warum es keinen Mehrfachdefinitionsfehler gibt. Dies kann durch eine andere Funktion verursacht werden, die von einigen Implementierungen bereitgestellt wird (z. B. GCC): Sie deklarieren Standardfunktionen als sogenannte schwache Symbole , sodass Sie sie durch Ihre eigenen Definitionen "ersetzen" können.

Diese beiden Faktoren zusammen erzeugen den Effekt, den Sie beobachten: Das Ersetzen von schwachen Symbolen führt ::absauch zum Ersetzen von std::abs. Wie gut dies mit dem Sprachstandard übereinstimmt, ist eine andere Geschichte ... Verlassen Sie sich auf keinen Fall auf dieses Verhalten - es wird von der Sprache nicht garantiert.

In GCC kann dieses Verhalten anhand des folgenden minimalistischen Beispiels reproduziert werden. Eine Quelldatei

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

Eine andere Quelldatei

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

In diesem Fall werden Sie auch feststellen, dass die neue Definition von ::foo( "Goodbye!") in der zweiten Quelldatei auch das Verhalten von beeinflusst N::foo. Beide Anrufe werden ausgegeben "Goodbye!". Wenn Sie die Definition von ::fooaus der zweiten Quelldatei entfernen , werden beide Aufrufe an die "ursprüngliche" Definition von ::fooausgegeben und ausgegeben "Hello!".


Die Erlaubnis des obigen 20.5.1.2/4 dient dazu, die Implementierung von zu vereinfachen <cmath>. Implementierungen dürfen einfach C-Stil enthalten <math.h>, dann die Funktionen neu deklarieren stdund einige C ++ - spezifische Ergänzungen und Optimierungen hinzufügen. Wenn die obige Erklärung die innere Mechanik des Problems richtig beschreibt, hängt ein Großteil davon von der Ersetzbarkeit schwacher Symbole für C-Versionen der Funktionen ab.

Beachten Sie, dass , wenn wir einfach global ersetzen intmit doublein dem obigen Programm wird der Code (unter GCC) „wie erwartet“ verhalten - es wird ausgegeben -5 5. Dies geschieht, weil die C-Standardbibliothek keine abs(double)Funktion hat. Indem abs(double)wir unsere eigenen deklarieren , ersetzen wir nichts.

Wenn wir aber nach dem Wechsel von intmit doubleauch von abszu wechseln , fabswird das ursprüngliche seltsame Verhalten in seiner vollen Pracht (Ausgabe -5 -5) wieder auftauchen .

Dies steht im Einklang mit der obigen Erklärung.

Ameise
quelle
Wie ich in der Quelle von cmath sehen kann, gibt es kein Gleiches using ::abs;für das. using ::asin;Sie können die Deklaration überschreiben. Ein weiterer zu erwähnender Punkt ist, dass die in std definierten Namespace-Funktionen nicht für int deklariert sind, sondern für double , float
Take_Care_
2
Aus Sicht des Standards ist das Verhalten gemäß [extern.names] / 4 undefiniert .
xskxzr
Aber als ich das #include<cmath>in meinem Code löschte , bekam ich die gleiche Antwort. "
Peter
@Peter Aber woher bekommst du dann std :: abs? - Möglicherweise wird es über ein anderes Include aufgenommen. An diesem Punkt kehren Sie zu dieser Erklärung zurück. (Es ist für den Compiler nicht wichtig, ob ein Header direkt oder indirekt enthalten ist.)
RM
@Peter: abskann auch in deklariert werden <cstdlib>, was implizit durch eingeschlossen sein kann <iostream>. Versuchen Sie, Ihre eigenen zu entfernen absund zu prüfen, ob sie noch kompiliert werden.
Am
13

Ihr Code verursacht undefiniertes Verhalten.

C ++ 17 [extern.names] / 4:

Jede mit externer Verknüpfung deklarierte Funktionssignatur aus der C-Standardbibliothek ist der Implementierung zur Verwendung als Funktionssignatur mit externer "C" - und externer "C ++" - Verknüpfung oder als Name des Namespace-Bereichs im globalen Namespace vorbehalten.

Sie können also keine Funktion mit demselben Prototyp wie die Standard C-Bibliotheksfunktion erstellen int abs(int);. Unabhängig davon, welche Header Sie tatsächlich einschließen oder ob diese Header auch C-Bibliotheksnamen in den globalen Namespace einfügen.

Es kann jedoch zu einer Überlastung kommen, abswenn Sie unterschiedliche Parametertypen angeben.

MM
quelle
1
"oder als Name des Namespace-Bereichs im globalen Namespace", damit er im globalen Namespace nicht überladen werden kann.
xskxzr
@xskxzr Ich bin mir nicht sicher über die Interpretation des von Ihnen zitierten Textes. Wenn davon ausgegangen wird, dass der Benutzer nichts von diesem Namen im globalen Namespace deklarieren kann, ist der vorherige Teil des von mir zitierten Textes redundant, ebenso wie die meisten von [extern.names] / 3. Was mich zu der Annahme bringt, dass hier etwas anderes beabsichtigt war.
MM