Một khai báo có thể ảnh hưởng đến không gian tên std không?


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

Tôi đã mong đợi rằng đầu ra sẽ là -55, nhưng đầu ra là -5-5.

Tôi tự hỏi tại sao trường hợp này sẽ xảy ra?

Nó có liên quan gì đến việc sử dụng stdhay không?


1
Việc triển khai của bạn abskhông chính xác.
Richard viết

31
@RichardCritten Đó là vấn đề. OP đang hỏi tại sao thêm cái này bị hỏng absảnh hưởng std::abs().
HolyBlackCat

11
Thật thú vị, tôi nhận được 55với tiếng kêu, -5-5với gcc.
Rakete1111

10
Cmake không phải là một trình biên dịch, mà là một hệ thống xây dựng. Bạn có thể sử dụng cmake để xây dựng với nhiều trình biên dịch khác nhau.
HolyBlackCat

5
Tôi có thể khuyên bạn chỉ cần có chức năng của mình return 0- điều đó sẽ tránh được việc mọi người nghĩ rằng bạn đã vô tình triển khai chức năng không chính xác và làm cho hành vi mong muốn và thực tế rõ ràng hơn.
Bernhard Barker

Câu trả lời:


92

Đặc tả ngôn ngữ cho phép triển khai thực hiện <cmath>bằng cách tuyên bố (và xác định) các chức năng tiêu chuẩn trong toàn cầu namespace và sau đó đưa chúng vào namespace stdbằng phương tiện của việc sử dụng-khai. Không xác định được liệu phương pháp này có được sử dụng hay không

20.5.1.2 Headers
4 [...] Tuy nhiên, trong thư viện chuẩn C ++, các khai báo (ngoại trừ các tên được định nghĩa là macro trong C) nằm trong phạm vi vùng tên (6.3.6) của vùng tên std. Không xác định được liệu các tên này (bao gồm bất kỳ quá tải nào được bổ sung trong các Khoản từ 21 đến 33 và Phụ lục D) lần đầu tiên được khai báo trong phạm vi không gian tên chung và sau đó được đưa vào không gian tên stdbằng cách sử dụng khai báo rõ ràng (10.3.3).

Rõ ràng, bạn đang xử lý một trong các triển khai quyết định theo cách tiếp cận này (ví dụ: GCC). Tức là triển khai của bạn cung cấp ::abs, trong khi std::abschỉ đơn giản là "tham chiếu" đến ::abs.

Một câu hỏi vẫn còn trong trường hợp này là tại sao ngoài tiêu chuẩn ::abs bạn có thể khai báo của riêng mình ::abs, tức là tại sao không có lỗi nhiều định nghĩa. Điều này có thể do một tính năng khác được cung cấp bởi một số triển khai (ví dụ: GCC): chúng khai báo các hàm tiêu chuẩn được gọi là ký hiệu yếu , do đó cho phép bạn "thay thế" chúng bằng định nghĩa của riêng bạn.

Hai yếu tố này kết hợp với nhau tạo ra hiệu ứng mà bạn quan sát: sự thay thế ký hiệu yếu của ::abscũng dẫn đến sự thay thế của std::abs. Điều này phù hợp với tiêu chuẩn ngôn ngữ đến mức nào là một câu chuyện khác ... Trong mọi trường hợp, đừng dựa vào hành vi này - nó không được đảm bảo bởi ngôn ngữ.

Trong GCC, hành vi này có thể được tái tạo bằng ví dụ tối giản sau. Một tệp nguồn

#include <iostream>

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

Một tệp nguồn khác

#include <iostream>

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

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

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

Trong trường hợp này, bạn cũng sẽ thấy rằng định nghĩa mới của ::foo( "Goodbye!") trong tệp nguồn thứ hai cũng ảnh hưởng đến hành vi của N::foo. Cả hai cuộc gọi sẽ xuất ra "Goodbye!". Và nếu bạn loại bỏ định nghĩa của::foo khỏi tệp nguồn thứ hai, cả hai lệnh gọi sẽ chuyển đến định nghĩa ::foovà đầu ra "gốc" "Hello!".


Sự cho phép được đưa ra bởi 20.5.1.2/4 ở trên là có để đơn giản hóa việc triển khai <cmath>. Việc triển khai chỉ được phép bao gồm C-style <math.h>, sau đó khai báo lại các hàm trongstd và thêm một số bổ sung và chỉnh sửa cụ thể của C ++. Nếu giải thích ở trên mô tả đúng cơ chế bên trong của vấn đề, thì một phần chính của nó phụ thuộc vào khả năng thay thế của các ký hiệu yếu đối với các phiên bản kiểu C của các hàm.

Lưu ý rằng nếu chúng ta chỉ thay thế toàn cục intbằng doubletrong chương trình trên, mã (trong GCC) sẽ hoạt động "như mong đợi" - nó sẽ xuất-5 5 . Điều này xảy ra vì thư viện chuẩn C không có abs(double)chức năng. Bằng cách khai báo riêng của abs(double)chúng tôi, chúng tôi không thay thế bất cứ điều gì.

Nhưng nếu sau khi chuyển từ intvới doublechúng tôi cũng chuyển từ abssangfabs , hành vi kỳ lạ ban đầu sẽ xuất hiện lại trong vinh quang đầy đủ của nó (đầu ra -5 -5).

Điều này phù hợp với giải thích trên.


như tôi có thể thấy trong nguồn cmath không có gì using ::abs;giống như using ::asin;vậy Bạn có thể ghi đè khai báo, một điểm khác cần đề cập là các hàm không gian tên được xác định trong std không được khai báo cho int mà là cho double , float
Take_Care_

2
Từ quan điểm của tiêu chuẩn, hành vi không được xác định theo [extern.names] / 4 .
xskxzr

Nhưng khi tôi xóa #include<cmath>trong mã của tôi, tôi đã cùng answer.`
Peter

@Peter Nhưng sau đó bạn lấy std :: abs từ đâu? - Nó có thể được đưa vào thông qua một bao gồm khác, lúc này bạn quay lại phần giải thích này. (Nó không quan trọng để trình biên dịch nếu một tiêu đề được bao gồm trực tiếp hoặc gián tiếp.)
RM

@Peter: abscũng có thể được khai báo trong <cstdlib>, có thể được đưa vào một cách ngầm định <iostream>. Hãy thử xóa của riêng bạn absvà xem liệu nó có còn biên dịch hay không.
AnT

13

Mã của bạn gây ra hành vi không xác định.

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

Mỗi chữ ký hàm từ thư viện chuẩn C được khai báo với liên kết bên ngoài được dành riêng cho việc triển khai để sử dụng như một chữ ký hàm với cả liên kết ngoài "C" và ngoại "C ++", hoặc như một tên của phạm vi không gian tên trong không gian tên chung.

Vì vậy, bạn không thể tạo một hàm có cùng nguyên mẫu với hàm thư viện C chuẩn int abs(int);. Bất kể tiêu đề nào bạn thực sự bao gồm hoặc liệu những tiêu đề đó cũng đặt tên thư viện C vào không gian tên chung.

Tuy nhiên, nó sẽ được phép quá tải absnếu bạn cung cấp các loại tham số khác nhau.


1
"hoặc là tên của phạm vi không gian tên trong không gian tên chung", vì vậy nó không thể bị quá tải trong không gian tên chung.
xskxzr

@xskxzr Tôi không chắc về cách giải thích văn bản bạn trích dẫn; nếu điều đó được hiểu là người dùng không thể khai báo bất kỳ thứ gì của tên đó trong không gian tên chung thì phần trước của văn bản mà tôi đã trích dẫn sẽ là thừa, cũng như hầu hết [extern.names] / 3. Điều này khiến tôi nghĩ rằng một cái gì đó khác đã được dự định ở đây.
MM
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.