선언이 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;
}

출력이 -5and 가 될 것으로 예상 5했지만 출력은 -5and -5입니다.

이 사건이 왜 일어날 지 궁금합니다.

사용과 관련이 std있습니까?


1
의 구현 abs이 잘못되었습니다.
리처드 Critten

31
@RichardCritten 그게 요점입니다. 이 깨진 부가하는 이유는 영업 이익의 묻고는 abs영향을 미칩니다 std::abs().
HolyBlackCat

11
재미있는, 나는 수 55그 소리와, -5-5GCC와.
Rakete1111

10
Cmake는 컴파일러가 아니라 빌드 시스템입니다. cmake를 사용하여 다양한 컴파일러로 빌드 할 수 있습니다.
HolyBlackCat

5
나는 아마도 당신이 단순히 당신의 기능을 가지고있는 것을 추천했을 것입니다. return 0그것은 사람들이 당신이 의도하지 않게 기능을 잘못 구현했다고 생각하고 원하고 실제적인 행동을 더 명확하게 만드는 것을 피했을 것입니다.
Bernhard Barker

답변:


92

언어 사양을 사용하면 전역 네임 스페이스 <cmath>에서 표준 함수를 선언 (및 정의) 한 다음 using-declarations를 통해 네임 스페이스로 가져와 구현을 구현할 수 있습니다 . 이 접근법이 사용되는지 여부는 지정되지 않았습니다.std

20.5.1.2 헤더
4 [...] 그러나 C ++ 표준 라이브러리에서 선언 (C에서 매크로로 정의 된 이름 제외)은 네임 스페이스의 네임 스페이스 범위 (6.3.6) 내에 있습니다 std. 이러한 이름 (21 ~ 33 절 및 부록 D에 추가 된 오버로드 포함)이 먼저 전역 네임 스페이스 범위 내에서 선언 된 다음 std명시 적 using-declarations (10.3.3)에 의해 네임 스페이스 에 삽입되는지 여부는 지정되지 않습니다 .

분명히이 접근법을 따르기로 결정한 구현 중 하나 (예 : GCC)를 다루고 있습니다. 즉, 구현은을 제공 ::abs하지만 std::abs단순히를 "참조"합니다 ::abs.

이 경우에 남아있는 한 가지 질문은 표준에 추가 ::abs하여 자신의을 선언 할 수 있었던 ::abs이유, 즉 다중 정의 오류가없는 이유입니다. 이는 일부 구현 (예 : GCC)에서 제공하는 다른 기능으로 인해 발생할 수 있습니다. 표준 기능을 소위 약한 기호 로 선언합니다. 하여 사용자가 자신의 정의로 "대체"할 수 있도록합니다.

이 두 가지 요소가 함께 관찰 된 효과를 생성합니다. 약한 기호 ::abs를 대체하면 std::abs. 이것이 언어 표준에 얼마나 잘 부합하는지는 다른 이야기입니다 ... 어쨌든이 행동에 의존하지 마십시오. 언어에 의해 보장되지 않습니다.

GCC에서이 동작은 다음과 같은 최소한의 예제로 재현 할 수 있습니다. 하나의 소스 파일

#include <iostream>

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

다른 소스 파일

#include <iostream>

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

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

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

이 경우 두 번째 소스 파일에서 ::foo( "Goodbye!") 의 새 정의가 의 동작에도 영향을 미치는 것을 관찰 할 수 있습니다 N::foo. 두 호출 모두 "Goodbye!". 그리고 ::foo두 번째 소스 파일에서 의 정의를 제거하면 두 호출 모두의 "원래"정의 ::foo및 출력으로 전달 "Hello!"됩니다.


위의 20.5.1.2/4에서 부여한 권한은 <cmath>. 구현은 단순히 C-style을 포함한 <math.h>다음 함수를 다시 선언하고 std일부 C ++ 관련 추가 및 조정을 추가 할 수 있습니다. 위의 설명이 문제의 내부 메커니즘을 적절하게 설명하는 경우 주요 부분은 C 스타일 버전 의 함수에 대한 약한 기호의 교체 가능성에 달려 있습니다.

우리는 단순히 세계적으로 대체 할 경우주의 intdouble는 출력 것이다 - 위의 프로그램에서, (GCC 아래) 코드는 "예상대로"작동합니다 -5 5. 이것은 C 표준 라이브러리에 abs(double)기능 이 없기 때문에 발생 합니다. 우리 자신을 선언함으로써 abs(double)우리는 아무것도 대체하지 않습니다.

그러나로 전환 한 후 경우 intdouble우리는 또한 전환 absfabs, 원래의 이상한 행동은 한창 (출력에 다시 나타납니다 -5 -5).

이것은 위의 설명과 일치합니다.


내가 cmath의 소스에서 볼 수없는이없는 using ::abs;에 대한처럼 using ::asin;당신은 선언을 대체 할 수 있도록 언급에 또 다른 포인트는 표준 네임 스페이스 기능에 정의 된이 인터넷 용 오히려 선언되지 않은 것입니다 더블 , 플로트
Take_Care_

2
표준보기에서 동작은 [extern.names] / 4 별로 정의되어 있지 않습니다 .
xskxzr 17:29에

하지만 #include<cmath>내 코드에서를 삭제했을 때 같은 답을 얻었습니다 .`
Peter

@Peter 그런데 어디에서 std :: abs를 얻고 있습니까? -다른 포함을 통해 포함되었을 수 있으며,이 시점에서이 설명으로 돌아갑니다. (헤더가 직접 또는 간접적으로 포함되어 있는지 컴파일러에 문제가되지 않습니다.)
RM

@Peter : abs<cstdlib>통해 암시 적으로 포함될 수있는에서도 선언 할 수 있습니다 <iostream>. 자신의 것을 제거 abs하고 여전히 컴파일되는지 확인하십시오.
AnT

13

코드로 인해 정의되지 않은 동작이 발생합니다.

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

외부 연결로 선언 된 C 표준 라이브러리의 각 함수 서명은 extern "C"및 extern "C ++"연결이있는 함수 서명으로 사용하거나 전역 네임 스페이스에서 네임 스페이스 범위의 이름으로 사용하기 위해 구현에 예약되어 있습니다.

따라서 표준 C 라이브러리 함수와 동일한 프로토 타입으로 함수를 만들 수 없습니다. int abs(int); . 실제로 포함하는 헤더 또는 해당 헤더가 C 라이브러리 이름을 전역 네임 스페이스에 넣는 지 여부에 관계없이.

그러나 abs다른 매개 변수 유형을 제공하면 오버로드가 허용됩니다 .


1
"또는 전역 네임 스페이스에서 네임 스페이스 범위의 이름"이므로 전역 네임 스페이스에서 오버로드 할 수 없습니다.
xskxzr

@xskxzr 인용 한 텍스트의 해석에 대해 잘 모르겠습니다. 사용자가 전역 네임 스페이스에서 그 이름의 어떤 것도 선언 할 수 없다는 것을 의미한다면, 내가 인용 한 텍스트의 이전 부분은 대부분의 [extern.names] / 3처럼 중복됩니다. 여기에 다른 무언가가 의도 된 것이라고 생각하게됩니다.
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.