#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;
}
Spodziewałem się, że wyjście będzie -5
i 5
, ale wyjście to -5
i -5
.
Zastanawiam się, dlaczego tak się stanie?
Czy ma to coś wspólnego z użyciem std
czy co?
abs
jest nieprawidłowa.abs
wpływastd::abs()
.5
i5
z clangiem-5
iz-5
gcc.return 0
- dzięki temu ludzie nie myśleliby, że nieumyślnie zaimplementowałeś funkcję nieprawidłowo, a pożądane i rzeczywiste zachowanie stałoby się wyraźniejsze.Odpowiedzi:
Specyfikacja języka umożliwia implementację implementacji
<cmath>
poprzez deklarowanie (i definiowanie) standardowych funkcji w globalnej przestrzeni nazw, a następnie przenoszenie ich do przestrzeni nazwstd
za pomocą deklaracji using. Nie określono, czy takie podejście jest stosowaneNajwyraźniej masz do czynienia z jedną z implementacji, która zdecydowała się na takie podejście (np. GCC). Oznacza to, że Twoja implementacja zapewnia
::abs
, astd::abs
po prostu „odnosi się” do::abs
.Pytanie, które pozostaje w tym przypadku, to dlaczego oprócz standardu
::abs
mogłeś zadeklarować swój własny::abs
, tj. Dlaczego nie ma błędu wielokrotnej definicji. Może to być spowodowane inną cechą udostępnianą przez niektóre implementacje (np. GCC): deklarują one standardowe funkcje jako tzw. Słabe symbole , dzięki czemu można je „zastąpić” własnymi definicjami.Te dwa czynniki razem tworzą efekt, który obserwujesz: słaba zamiana symbolu
::abs
powoduje również zastąpieniestd::abs
. Jak dobrze to zgadza się ze standardem językowym, to inna historia… W każdym razie nie polegaj na tym zachowaniu - nie gwarantuje tego język.W GCC to zachowanie można odtworzyć na następującym minimalistycznym przykładzie. Jeden plik źródłowy
Inny plik źródłowy
W takim przypadku zauważysz również, że nowa definicja
::foo
("Goodbye!"
) w drugim pliku źródłowym również wpływa na zachowanieN::foo
. Zostaną wyświetlone oba wywołania"Goodbye!"
. A jeśli usuniesz definicję::foo
z drugiego pliku źródłowego, oba wywołania będą kierowane do „oryginalnej” definicji::foo
i danych wyjściowych"Hello!"
.Zezwolenie udzielone przez powyższy 20.5.1.2/4 ma na celu uproszczenie implementacji
<cmath>
. Implementacje mogą po prostu zawierać styl C<math.h>
, a następnie ponownie zadeklarować funkcjestd
i dodać niektóre C ++ - specyficzne dodatki i poprawki. Jeśli powyższe wyjaśnienie właściwie opisuje wewnętrzną mechanikę problemu, to większa jego część zależy od zastępowalności słabych symboli na wersji funkcji w stylu C.Zauważ, że jeśli po prostu globalnie zastąpić
int
zedouble
w powyższym programie, kod (pod GCC) będą zachowywać się „jak oczekiwano” - to będzie wyjście-5 5
. Dzieje się tak, ponieważ standardowa biblioteka C nie maabs(double)
funkcji. Deklarując własneabs(double)
, niczego nie zastępujemy.Ale jeśli po przełączeniu z
int
withdouble
również zmienimy się zabs
nafabs
, oryginalne dziwne zachowanie pojawi się ponownie w pełnej okazałości (wyjście-5 -5
).Jest to zgodne z powyższym wyjaśnieniem.
źródło
using ::abs;
podobnego dousing ::asin;
więc możesz przesłonić deklarację, kolejną kwestią, o której warto wspomnieć, jest to, że zdefiniowane w std funkcje przestrzeni nazw nie są zadeklarowane dla int, ale raczej dla double , float#include<cmath>
usunąłem w moim kodzie, otrzymałem tę samą odpowiedź. ”abs
można również zadeklarować w<cstdlib>
, co może być niejawnie włączone za pośrednictwem<iostream>
. Spróbuj usunąć własneabs
i zobacz, czy nadal się kompiluje.Twój kod powoduje niezdefiniowane zachowanie.
C ++ 17 [extern.names] / 4:
Nie możesz więc utworzyć funkcji z tym samym prototypem, co funkcja standardowej biblioteki C.
int abs(int);
. Bez względu na to, które nagłówki faktycznie dołączasz, lub czy te nagłówki również umieszczają nazwy bibliotek C w globalnej przestrzeni nazw.Jednak byłoby dozwolone przeciążenie,
abs
jeśli podasz różne typy parametrów.źródło