IT story

이 포인터 사용을 예측할 수 없게 만드는 것은 무엇입니까?

hot-time 2020. 8. 5. 07:42
반응형

이 포인터 사용을 예측할 수 없게 만드는 것은 무엇입니까?


나는 현재 포인터를 배우고 있으며 교수는이 코드를 예제로 제공했습니다.

//We cannot predict the behavior of this program!

#include <iostream>
using namespace std;

int main()
{
    char * s = "My String";
    char s2[] = {'a', 'b', 'c', '\0'};

    cout << s2 << endl;

    return 0;
}

그는 우리가 프로그램의 행동을 예측할 수 없다는 의견을 썼습니다. 정확히 예측할 수없는 것은 무엇입니까? 나는 아무 문제가 없다.


프로그램의 동작이 잘못되어 존재하지 않습니다.

char* s = "My String";

이것은 불법입니다. 2011 년 이전에는 12 년 동안 사용이 중단되었습니다.

올바른 줄은 다음과 같습니다.

const char* s = "My String";

그 외에는 프로그램이 좋습니다. 교수님은 위스키를 덜 마셔야합니다!


답은 컴파일하려는 C ++ 표준에 따라 다릅니다. 이 코드를 제외하고 모든 코드는 모든 표준에서 완벽하게 구성되어 있습니다.

char * s = "My String";

이제 문자열 리터럴에는 유형이 const char[10]있으며 상수가 아닌 포인터를 초기화하려고합니다. char문자열 리터럴 계열 이외의 다른 모든 유형의 경우 이러한 초기화는 항상 불법입니다. 예를 들면 다음과 같습니다.

const int arr[] = {1};
int *p = arr; // nope!

그러나 C ++ 11 이전의 문자열 리터럴의 경우 §4.2 / 2에서 예외가 발생했습니다.

넓은 문자열 리터럴이 아닌 문자열 리터럴 (2.13.4)은 " pointer to char " 유형의 rvalue로 변환 될 수 있습니다 . [...]. 두 경우 모두 결과는 배열의 첫 번째 요소에 대한 포인터입니다. 이 변환은 명시적인 적절한 포인터 대상 유형이있는 경우에만 고려되며 lvalue에서 rvalue로 변환해야하는 일반적인 경우는 아닙니다. [참고 : 이 변환은 더 이상 사용되지 않습니다 . 부속서 D 참조 ]

따라서 C ++ 03에서는 코드가 완벽하게 (더 이상 사용되지 않지만) 명확하고 예측 가능한 동작이 있습니다.

C ++ 11에는 해당 블록이 존재하지 않습니다-로 변환 된 문자열 리터럴에 대한 예외는 없으므로 char*코드는 int*방금 제공 와 같이 형식이 잘못되었습니다 . 컴파일러는 진단을 내려야 할 의무가 있으며, 이상적으로 C ++ 유형 시스템을 명백히 위반하는 경우에는 컴파일러가 이와 관련하여 (예 : 경고 발생) 준수 할뿐만 아니라 실패 할 것으로 예상합니다. 노골적인.

코드는 이상적으로 컴파일되지 않아야하지만 gcc와 clang 모두에서 수행됩니다 (이 유형 시스템 구멍이 10 년 이상 사용되지 않더라도 작은 이득으로 깨질 수있는 많은 코드가 있기 때문에 가정합니다). 코드의 형식이 잘못되었으므로 코드 동작이 무엇인지 추론하는 것이 합리적이지 않습니다. 그러나이 특정 사례와 이전에 허용 된 이력을 고려할 때 결과 코드가 암시 적 const_cast것처럼 해석하는 것은 무리가 없다고 생각하지 않습니다 .

const int arr[] = {1};
int *p = const_cast<int*>(arr); // OK, technically

그것으로, 당신은 실제로 s다시 만지지 않기 때문에 나머지 프로그램은 완벽하게 좋습니다 . 읽기 created- const비를 통해 객체를 const포인터 것은 무방하다. 이러한 포인터를 통해 생성 된 객체를 작성 하는 const것은 정의되지 않은 동작입니다.

std::cout << *p; // fine, prints 1
*p = 5;          // will compile, but undefined behavior, which
                 // certainly qualifies as "unpredictable"

s코드의 어느 곳에서나 수정이 없으므로 프로그램은 C ++ 03에서 훌륭하고 C ++ 11에서 컴파일에 실패하지만 어쨌든 수행해야합니다. 컴파일러가 허용하는 경우 여전히 정의되지 않은 동작이 없습니다 † . 컴파일러가 여전히 C ++ 03 규칙을 [부정확하게] 해석하는 것을 허용하면서 "예측할 수없는"동작으로 이어질 수있는 것은 없습니다. s그래도 쓰면 모든 베팅이 종료됩니다. C ++ 03 및 C ++ 11 모두에서.


† 정의로 잘못된 형식의 코드는 합리적인 행동을 기대
하지 않습니다. ‡ 그렇지 않으면 Matt McNabb의 답변 참조


다른 답변은 const char배열 이에 할당되어 있기 때문에 C ++ 11 에서이 프로그램이 잘못 형성되었다는 것을 다루었 습니다 char *.

그러나 C ++ 11 이전에는 프로그램이 잘못 구성되었습니다.

operator<<오버로드에 있습니다 <ostream>. iostream포함 요구 사항 ostream이 C ++ 11에 추가되었습니다.

역사적으로, 대부분의 구현에는 구현의 용이성 또는 더 나은 QoI를 제공하기 위해 어쨌든 iostream포함되어 ostream있었습니다.

그러나 오버로드 를 정의하지 않고 클래스 iostream만 정의하면 됩니다.ostreamoperator<<


The only slightly wrong thing that I see with this program is that you're not supposed to assign a string literal to a mutable char pointer, though this is often accepted as a compiler extension.

Otherwise, this program appears well-defined to me:

  • The rules that dictate how character arrays become character pointers when passed as parameters (such as with cout << s2) are well-defined.
  • The array is null-terminated, which is a condition for operator<< with a char* (or a const char*).
  • #include <iostream> includes <ostream>, which in turn defines operator<<(ostream&, const char*), so everything appears to be in place.

You can't predict the behaviour of the compiler, for reasons noted above. (It should fail to compile, but may not.)

If compilation succeeds, then the behaviour is well-defined. You certainly can predict the behaviour of the program.

If it fails to compile, there is no program. In a compiled language, the program is the executable, not the source code. If you don't have an executable, you don't have a program, and you can't talk about behaviour of something that doesn't exist.

So I'd say your prof's statement is wrong. You can't predict the behaviour of the compiler when faced with this code, but that's distinct from the behaviour of the program. So if he's going to pick nits, he'd better make sure he's right. Or, of course, you might have misquoted him and the mistake is in your translation of what he said.


As others have noted, the code is illegitimate under C++11, although it was valid under earlier versions. Consequently, a compiler for C++11 is required to issue at least one diagnostic, but behavior of the compiler or the remainder of the build system is unspecified beyond that. Nothing in the Standard would forbid a compiler from exiting abruptly in response to an error, leaving a partially-written object file which a linker might think was valid, yielding a broken executable.

Although a good compiler should always ensure before it exits that any object file it is expected to have produced will be either valid, non-existent, or recognizable as invalid, such issues fall outside the jurisdiction of the Standard. While there have historically been (and may still be) some platforms where a failed compilation can result in legitimate-appearing executable files that crash in arbitrary fashion when loaded (and I've had to work with systems where link errors often had such behavior), I would not say that the consequences of syntax errors are generally unpredictable. On a good system, an attempted build will generally either produce an executable with a compiler's best effort at code generation, or won't produce an executable at all. Some systems will leave behind the old executable after a failed build, since in some cases being able to run the last successful build may be useful, but that can also lead to confusion.

My personal preference would be for disk-based systems to to rename the output file, to allow for the rare occasions when that executable would be useful while avoiding the confusion that can result from mistakenly believing one is running new code, and for embedded-programming systems to allow a programmer to specify for each project a program that should be loaded if a valid executable is not available under the normal name [ideally something which which safely indicates the lack of a useable program]. An embedded-systems tool-set would generally have no way of knowing what such a program should do, but in many cases someone writing "real" code for a system will have access to some hardware-test code that could easily be adapted to the purpose. I don't know that I've seen the renaming behavior, however, and I know that I haven't seen the indicated programming behavior.

참고URL : https://stackoverflow.com/questions/31816473/what-makes-this-usage-of-pointers-unpredictable

반응형