Bisakah deklarasi mempengaruhi namespace std?

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;
}

Saya berharap bahwa outputnya adalah -5dan 5, tetapi outputnya adalah -5dan -5.

Saya heran kenapa kasus ini bisa terjadi?

Apakah ada hubungannya dengan penggunaan stdatau apa?

Peter
sumber
1
Penerapan Anda abssalah.
Richard Critten
31
@Richaeldia Itulah intinya. OP bertanya mengapa menambahkan absefek rusak ini std::abs().
HolyBlackCat
11
Menarik, saya mendapatkan 5dan 5dengan dentang, -5dan -5dengan gcc.
Rakete1111
10
Cmake bukanlah kompiler, melainkan sistem build. Anda dapat menggunakan cmake untuk membangun dengan berbagai kompiler.
HolyBlackCat
5
Saya mungkin akan merekomendasikan Anda untuk memiliki fungsi Anda return 0- yang akan menghindari orang berpikir Anda tidak sengaja menerapkan fungsi secara tidak benar dan membuat perilaku yang diinginkan dan sebenarnya lebih jelas.
Bernhard Barker

Jawaban:

92

Spesifikasi bahasa memungkinkan implementasi untuk diimplementasikan <cmath>dengan mendeklarasikan (dan mendefinisikan) fungsi standar dalam namespace global dan kemudian membawanya ke namespace stddengan menggunakan deklarasi menggunakan. Tidak ditentukan apakah pendekatan ini digunakan

20.5.1.2 Headers
4 [...] Namun, di pustaka standar C ++, deklarasi (kecuali untuk nama yang didefinisikan sebagai makro di C) berada dalam cakupan namespace (6.3.6) dari namespace std. Tidak ditentukan apakah nama-nama ini (termasuk kelebihan beban yang ditambahkan dalam Klausul 21 hingga 33 dan Lampiran D) pertama kali dideklarasikan dalam cakupan namespace global dan kemudian dimasukkan ke dalam namespace stddengan menggunakan deklarasi penggunaan eksplisit (10.3.3).

Rupanya, Anda berurusan dengan salah satu implementasi yang memutuskan untuk mengikuti pendekatan ini (misalnya GCC). Yaitu implementasi Anda menyediakan ::abs, sementara std::abshanya "mengacu" ke ::abs.

Satu pertanyaan yang tersisa dalam kasus ini adalah mengapa selain standar ::absAnda dapat mendeklarasikan milik Anda sendiri ::abs, yaitu mengapa tidak ada kesalahan definisi ganda. Hal ini mungkin disebabkan oleh fitur lain yang disediakan oleh beberapa implementasi (mis. GCC): mereka mendeklarasikan fungsi standar yang disebut simbol lemah , sehingga memungkinkan Anda untuk "menggantinya" dengan definisi Anda sendiri.

Kedua faktor ini bersama-sama menciptakan efek yang Anda amati: penggantian simbol lemah ::absjuga menghasilkan penggantian std::abs. Seberapa baik hal ini sesuai dengan standar bahasa adalah cerita yang berbeda ... Bagaimanapun, jangan mengandalkan perilaku ini - ini tidak dijamin oleh bahasa.

Di GCC, perilaku ini dapat direproduksi dengan contoh minimalis berikut. Satu file sumber

#include <iostream>

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

File sumber lain

#include <iostream>

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

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

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

Dalam kasus ini, Anda juga akan mengamati bahwa definisi baru ::foo( "Goodbye!") di file sumber kedua juga memengaruhi perilaku N::foo. Kedua panggilan akan keluar "Goodbye!". Dan jika Anda menghapus definisi dari ::foofile sumber kedua, kedua panggilan akan dikirim ke definisi "asli" ::foodan keluaran "Hello!".


Izin yang diberikan oleh 20.5.1.2/4 di atas ada untuk menyederhanakan implementasi <cmath>. Implementasi diperbolehkan untuk hanya memasukkan C-style <math.h>, kemudian mendeklarasikan kembali fungsi-fungsi tersebut stddan menambahkan beberapa tambahan dan tweak khusus C ++. Jika penjelasan di atas dengan tepat mendeskripsikan mekanisme bagian dalam dari masalah tersebut, maka sebagian besar bergantung pada kemampuan penggantian simbol yang lemah untuk versi C-style dari fungsi tersebut.

Perhatikan bahwa jika kita hanya mengganti secara global intdengan doubledalam program di atas, kode (di bawah GCC) akan berperilaku "seperti yang diharapkan" - itu akan keluar -5 5. Ini terjadi karena pustaka standar C tidak memiliki abs(double)fungsi. Dengan menyatakan milik kita sendiri abs(double), kita tidak mengganti apapun.

Tetapi jika setelah beralih dari intdengan doublekami juga beralih dari abske fabs, perilaku aneh yang asli akan muncul kembali dalam kemuliaan penuh (output -5 -5).

Hal ini sesuai dengan penjelasan di atas.

Semut
sumber
seperti yang saya lihat di sumber cmath tidak ada using ::abs;suka untuk using ::asin;sehingga Anda dapat menimpa deklarasi, hal lain yang perlu disebutkan adalah bahwa fungsi namespace yang ditentukan dalam std tidak dideklarasikan untuk int melainkan untuk ganda , float
Take_Care_
2
Dari pandangan standar, perilaku tidak ditentukan per [extern.names] / 4 .
xskxzr
Tetapi ketika saya menghapus #include<cmath>di kode saya, saya mendapat jawaban yang sama.`
Peter
@ Peter Tapi lalu dari mana Anda mendapatkan std :: abs? - Ini mungkin dimasukkan melalui penyertaan lain, di mana Anda kembali ke penjelasan ini. (Tidak masalah bagi kompiler jika header disertakan secara langsung atau tidak langsung.)
RM
@Peter: absdapat dideklarasikan <cstdlib>juga, yang mungkin secara implisit disertakan melalui <iostream>. Coba hapus milik Anda absdan lihat apakah masih terkompilasi.
AnT
13

Kode Anda menyebabkan perilaku tidak terdefinisi.

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

Setiap tanda tangan fungsi dari pustaka standar C yang dideklarasikan dengan tautan eksternal dicadangkan untuk implementasi untuk digunakan sebagai tanda tangan fungsi dengan tautan "C" dan "C ++" eksternal, atau sebagai nama cakupan namespace di namespace global.

Jadi Anda tidak bisa membuat fungsi dengan prototipe yang sama dengan fungsi pustaka C Standar int abs(int);. Terlepas dari header mana yang sebenarnya Anda sertakan atau apakah header tersebut juga menempatkan nama library C ke dalam namespace global.

Namun, akan diizinkan untuk membebani absjika Anda memberikan jenis parameter yang berbeda.

MM
sumber
1
"atau sebagai nama cakupan namespace di namespace global", jadi tidak dapat dibebani berlebihan di namespace global.
xskxzr
@xskxzr Saya tidak yakin tentang interpretasi teks yang Anda kutip; jika ini diartikan bahwa pengguna tidak dapat mendeklarasikan apa pun dari nama itu di namespace global maka bagian sebelumnya dari teks yang saya kutip akan menjadi mubazir, seperti kebanyakan [extern.names] / 3. Yang membuat saya berpikir bahwa ada sesuatu yang dimaksudkan di sini.
MM