일부 컴파일러가 동일한 문자열 리터럴에 동일한 주소를 사용하는 이유는 무엇입니까?


92

https://godbolt.org/z/cyBiWY

'some'MSVC에서 생성 한 어셈블러 코드에서 두 개의 리터럴을 볼 수 있지만 clang과 gcc가있는 것은 하나뿐입니다. 이로 인해 코드 실행 결과가 완전히 다릅니다.

static const char *A = "some";
static const char *B = "some";

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

누구든지 이러한 컴파일 출력의 차이점과 유사점을 설명 할 수 있습니까? 최적화가 요청되지 않았는데도 clang / gcc가 무언가를 최적화하는 이유는 무엇입니까? 이것은 일종의 정의되지 않은 동작입니까?

또한 선언을 아래 표시된 것으로 변경하면 clang / gcc / msvc가 "some"어셈블러 코드에 전혀 남기지 않는다는 것을 알 수 있습니다. 행동이 다른 이유는 무엇입니까?

static const char A[] = "some";
static const char B[] = "some";

4
stackoverflow.com/a/52424271/1133179 표준 따옴표와 함께 밀접하게 관련된 질문에 대한 좋은 관련 답변입니다.
luk32


6
MSVC의 경우 / GF 컴파일러 옵션이이 동작을 제어합니다. docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd

1
참고로 이것은 함수에서도 발생할 수 있습니다.
user541686

답변:


109

이것은 정의되지 않은 동작이 아니라 지정되지 않은 동작입니다. 대한 문자열 리터럴 ,

컴파일러는 동일하거나 겹치는 문자열 리터럴에 대한 저장소를 결합 할 수 있지만 필수는 아닙니다. 즉, 포인터로 비교할 때 동일한 문자열 리터럴이 동일하게 비교할 수도 있고 그렇지 않을 수도 있습니다.

즉,의 결과 A == Btrue또는 false일 수 있으며 의존해서는 안됩니다.

표준에서 [lex.string] / 16 :

모든 문자열 리터럴이 구별되는지 (즉, 겹치지 않는 객체에 저장 됨), 문자열 리터럴의 연속적인 평가가 동일한 객체 또는 다른 객체를 산출하는지 여부는 지정되지 않습니다.


36

다른 답변은 포인터 주소가 다를 것으로 예상 할 수없는 이유를 설명했습니다. 그러나 당신은 쉽게 보장하는 방식으로이 문제를 다시 작성할 수 AB그렇지 않은 동일 비교 :

static const char A[] = "same";
static const char B[] = "same";// but different

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

차이점은 존재 AB지금은 문자의 배열입니다. 이것은 그들이 포인터가 아니며 두 정수 변수의 주소와 마찬가지로 주소가 구별되어야 함을 의미합니다. 이 포인터와 배열은 교환 보인다 (수 있기 때문에 C ++이 혼란 operator*operator[]동일하게 동작하는 것),하지만 그들은 정말 다릅니다. 예를 들어 뭔가 const char *A = "foo"; A++;완벽하게 합법적이지만 const char A[] = "bar"; A++;그렇지 않습니다.

차이점에 대해 생각하는 한 가지 방법 char A[] = "..."은 "나에게 메모리 블록을 제공하고 ...뒤에 오는 문자로 채우십시오 \0" char *A= "..."라고 말하는 반면 " ...뒤에 오는 문자를 찾을 수있는 주소를 제공하십시오"라고 말하는 것 \0입니다.


8
왜 다른지 설명 할 수 있다면 더 나은 대답이 될 것 입니다.
Mark Ransom

참고 *pp[0]뿐 정의 "동일하게 동작하는 것"생략 있다 (동일한 것을 제공 p+0 == p하기 때문에 식별 인 관계 0포인터 - 정수 또한 중성 원소이다). 결국 p[i]은로 정의됩니다 *(p+i). 대답은 좋은 지적입니다.
피터 - 분석 재개 모니카

typeof(*p)그리고 typeof(p[0])둘 다 char그래서 다를 수있는 정말 많이 남아 있지 않습니다. 나는 의미가 너무 다르기 때문에 '동일하게 행동하는 것처럼 보인다'가 최고의 표현이 아니라는 데 동의합니다. 귀하의 게시물은 C ++ 배열의 요소에 액세스하는 가장 좋은 방법 0[p]1[p], 2[p]등을 상기 시켰습니다 . 이것은 적어도 C 프로그래밍 언어 이후에 태어난 사람들을 혼동하고 싶을 때 전문가들이하는 방법입니다.
tobi_s


이것은 흥미롭고 C FAQ에 대한 링크를 추가하고 싶었지만 관련 질문이 많이 있다는 것을 깨달았지만 여기 에서이 질문의 요점에 맞지 않는 것 같습니다.
tobi_s

23

여부 컴파일러이 선택하는가에 대해 동일한 문자열의 위치를 사용 A하고 B구현까지입니다. 공식적으로 코드의 동작이 지정되지 않았다고 말할 수 있습니다 .

두 선택 모두 C ++ 표준을 올바르게 구현합니다.


코드의 동작은 코드가 처음 실행되기 전에 지정되지 않은 방식으로 예외를 발생 시키거나 아무것도하지 않는 것 입니다. 그렇다고 전체 동작이 지정되지 않았 음을 의미하지는 않습니다. 단지 컴파일러가 동작이 처음 관찰되기 전에 적합하다고 판단되는 방식으로 동작을 선택할 수 있다는 의미입니다.
supercat

3

이것은 종종 "문자열 풀링"이라고하는 공간 절약을위한 최적화입니다. 다음은 MSVC에 대한 문서입니다.

https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

따라서 명령 줄에 / GF를 추가하면 MSVC에서 동일한 동작을 볼 수 있습니다.

그건 그렇고 당신은 아마도 그런 포인터를 통해 문자열을 비교해서는 안되며, 괜찮은 정적 분석 도구는 해당 코드를 결함으로 표시합니다. 실제 포인터 값이 아니라 그들이 가리키는 것을 비교해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.