나는 책에서이 줄을 읽었습니다.
실제로 C ++ 함수가 특정 변수의 값을 변경할지 여부를 결정할 수있는 컴파일러를 빌드하는 것은 불가능합니다.
이 단락은 const-ness를 확인할 때 컴파일러가 보수적 인 이유에 대해 이야기하고있었습니다.
왜 그런 컴파일러를 만드는 것이 불가능합니까?
컴파일러는 항상 변수가 재 할당되었는지, 상수가 아닌 함수가 호출되는지, 아니면 상수가 아닌 매개 변수로 전달되는지 확인할 수 있습니다.
나는 책에서이 줄을 읽었습니다.
실제로 C ++ 함수가 특정 변수의 값을 변경할지 여부를 결정할 수있는 컴파일러를 빌드하는 것은 불가능합니다.
이 단락은 const-ness를 확인할 때 컴파일러가 보수적 인 이유에 대해 이야기하고있었습니다.
왜 그런 컴파일러를 만드는 것이 불가능합니까?
컴파일러는 항상 변수가 재 할당되었는지, 상수가 아닌 함수가 호출되는지, 아니면 상수가 아닌 매개 변수로 전달되는지 확인할 수 있습니다.
답변:
왜 그런 컴파일러를 만드는 것이 불가능합니까?
같은 이유로 주어진 프로그램이 종료되는지 여부를 결정하는 프로그램을 작성할 수 없습니다. 이것은 중지 문제 로 알려져 있으며 계산할 수없는 문제 중 하나입니다.
명확 하게 말하면 함수가 어떤 경우에 변수 를 변경하는지 확인할 수있는 컴파일러를 작성할 수 있지만 함수가 변수를 변경 (또는 중지) 할 것인지 여부를 안정적으로 알려주 는 컴파일러를 작성할 수 없습니다. 가능한 모든 기능.
다음은 간단한 예입니다.
void foo() {
if (bar() == 0) this->a = 1;
}
컴파일러는 코드를보고 foo
변경 여부 를 어떻게 결정할 수 a
있습니까? 실행 여부는 함수 외부의 조건, 즉 bar
. 중지 문제를 계산할 수 없다는 증거에는 그 이상이 있지만 연결된 Wikipedia 기사 (및 모든 계산 이론 교과서)에서 이미 잘 설명되어 있으므로 여기서 올바르게 설명하지 않겠습니다.
그러한 컴파일러가 존재한다고 상상해보십시오. 편의를 위해 전달 된 함수가 주어진 변수를 수정하면 1을 반환하고 함수가 수정하지 않으면 0을 반환하는 라이브러리 함수를 제공한다고 가정 해 보겠습니다. 그러면이 프로그램은 무엇을 인쇄해야합니까?
int variable = 0;
void f() {
if (modifies_variable(f, variable)) {
/* do nothing */
} else {
/* modify variable */
variable = 1;
}
}
int main(int argc, char **argv) {
if (modifies_variable(f, variable)) {
printf("Modifies variable\n");
} else {
printf("Does not modify variable\n");
}
return 0;
}
f
변수를 수정할 수 있는지 여부가 아니라 변수를 수정하는지 여부입니다. 정답입니다.
modifies_variable
컴파일러 소스에서 코드를 복사 / 붙여 넣기하는 것을 막지 못해 인수를 완전히 무효화합니다. (오픈 소스로 가정하지만 요점은 분명해야 함)
"변수 를 수정하는 실행 경로가 있음 " 과 " 이 입력이 주어지면 변수를 수정하거나 수정 하지 않을 것 "과 혼동하지 마십시오 .
전자는 불투명 한 술어 결정 이라고 하며 결정하기가 거의 불가능합니다. 중지 문제로 인한 감소를 제외하고는 입력이 알 수없는 소스 (예 : 사용자)에서 올 수 있음을 지적 할 수 있습니다. 이것은 C ++뿐만 아니라 모든 언어에 해당 됩니다.
그러나 후자의 명령문 은 모든 최적화 컴파일러가 수행하는 구문 분석 트리를 보면 확인할 수 있습니다. 그들이하는 이유는 순수 함수 (그리고 참조 적으로 투명 함의 일부 정의를 위한 참조 적으로 투명한 함수 ) 는 쉽게 inlinable하거나 컴파일 타임에 값을 결정하는 것과 같이 적용 할 수있는 모든 종류의 멋진 최적화를 가지고 있기 때문입니다. 그러나 함수가 순수한지 알기 위해서는 변수를 수정할 수 있는지 알아야 합니다.
따라서 C ++에 대한 놀라운 진술로 보이는 것은 실제로 모든 언어에 대한 사소한 진술입니다.
"C ++ 함수가 특정 변수의 값을 바꿀지 여부"의 핵심 단어는 "will"이라고 생각합니다. C ++ 함수 가 특정 변수의 값 을 변경할 수 있는지 여부를 확인하는 컴파일러를 빌드하는 것은 확실히 가능 합니다. 변경이 일어날 것이라고 확신 할 수는 없습니다.
void maybe(int& val) {
cout << "Should I change value? [Y/N] >";
string reply;
cin >> reply;
if (reply == "Y") {
val = 42;
}
}
const
-ness 검사 에 대해 이야기 할 때 염두에 둔 것이라고 믿는다.
이는 수행 될 수 있으며 컴파일러는 일부 함수에 대해 항상 수행합니다. 예를 들어 간단한 인라인 접근 자 또는 많은 순수 함수에 대한 사소한 최적화입니다.
불가능한 것은 일반적인 경우에 그것을 아는 것입니다.
시스템 호출이나 다른 모듈에서 오는 함수 호출 또는 잠재적으로 재정의 된 메서드에 대한 호출이있을 때마다 해커가 스택 오버플로를 사용하여 관련없는 변수를 변경하는 적대적인 인수를 포함하여 모든 일이 발생할 수 있습니다.
그러나 const를 사용하고, 전역을 피하고, 포인터에 대한 참조를 선호하고, 관련없는 작업에 대한 변수 재사용을 피해야합니다. 이렇게하면 공격적인 최적화를 수행 할 때 컴파일러의 수명이 더 쉬워집니다.
이를 설명하는 데는 여러 가지 방법이 있으며 그 중 하나는 중지 문제입니다 .
계산 가능성 이론에서 중단 문제는 "임의의 컴퓨터 프로그램에 대한 설명이 주어지면 프로그램 실행이 완료되는지 또는 계속 실행되는지 결정"과 같이 설명 할 수 있습니다. 이것은 프로그램과 입력이 주어지면 프로그램이 해당 입력으로 실행될 때 결국 중지 될 것인지 또는 영원히 실행될 것인지를 결정하는 문제와 동일합니다.
Alan Turing은 1936 년에 가능한 모든 프로그램 입력 쌍에 대한 정지 문제를 해결하는 일반적인 알고리즘이 존재할 수 없음을 증명했습니다.
다음과 같은 프로그램을 작성하면 :
do tons of complex stuff
if (condition on result of complex stuff)
{
change value of x
}
else
{
do not change value of x
}
x
변화 의 가치가 있습니까? 이를 확인하려면 먼저 do tons of complex stuff
부품으로 인해 조건이 발생하는지 여부를 확인해야합니다. 그것은 컴파일러가 할 수없는 일입니다.
정지 문제를 직접 사용하는 답이 없다는 사실에 정말 놀랐습니다! 이 문제에서 중단 문제로 매우 간단하게 줄일 수 있습니다.
컴파일러가 함수가 변수 값을 변경했는지 여부를 알 수 있다고 상상해보십시오. 그러면 프로그램의 나머지 부분에서 x 값이 모든 호출에서 추적 될 수 있다고 가정하면 다음 함수가 y 값을 변경하는지 여부를 확실히 알 수 있습니다.
foo(int x){
if(x)
y=1;
}
이제 우리가 좋아하는 프로그램에 대해 다음과 같이 다시 작성해 보겠습니다.
int y;
main(){
int x;
...
run the program normally
...
foo(x);
}
프로그램이 y의 값을 변경하는 경우에만 종료됩니다. 종료하기 전에 foo ()가 마지막으로 수행하는 작업입니다. 이것은 우리가 중단 문제를 해결했음을 의미합니다!
위의 축소가 보여주는 것은 변수의 값이 변경되는지 여부를 결정하는 문제가 적어도 중단 문제만큼 어렵다는 것입니다. 중지 문제는 계산할 수없는 것으로 알려져 있으므로이 문제도 마찬가지입니다.
y
. foo()
빨리 돌아온 다음 main()
나가는 것처럼 보입니다 . (또한, 당신은 foo()
논쟁없이 전화 하고 있습니다 ... 그것은 내 혼란의 일부입니다.)
함수가 컴파일러가 소스를 "보지"않는 다른 함수를 호출하자마자 변수가 변경되었다고 가정해야합니다. 그렇지 않으면 아래에서 더 이상 문제가 발생할 수 있습니다. 예를 들어 "foo.cpp"에 다음이 있다고 가정합니다.
void foo(int& x)
{
ifstream f("f.dat", ifstream::binary);
f.read((char *)&x, sizeof(x));
}
"bar.cpp"에 다음이 있습니다.
void bar(int& x)
{
foo(x);
}
컴파일러 x
는 변경되지 않는 (또는 더 적절하게 변경되고있는) 것을 어떻게 "알 수 bar
있습니까?"
이것이 충분히 복잡하지 않다면 우리는 더 복잡한 것을 생각 해낼 수 있다고 확신합니다.
const_cast
in foo를 추가하면 여전히 x
변경 될 것입니다. const
변수 를 변경해서는 안된다는 계약을 위반할 것입니다. 하지만 무엇이든 "more const"로 변환 할 수 있고 const_cast
존재하기 때문에 언어의 설계자들은 const
가치가 변화 할 필요가 있다고 믿을만한 좋은 이유가 때때로 있다는 생각을 분명히 가지고있었습니다 .
일반적 으로 지적했듯이 컴파일러가 변수 가 변경 되는지 여부를 결정하는 것은 불가능 합니다.
CONST 다움을 검토 한 결과, 관심의 문제는 변수가 있다면 것으로 보인다 수 함수에 의해 변경 될 수있다. 포인터를 지원하는 언어에서는 이것조차 어렵습니다. 포인터로 다른 코드가 수행하는 작업을 제어 할 수 없으며 외부 소스에서 읽을 수도 있습니다. 메모리에 대한 액세스를 제한하는 언어에서는 이러한 유형의 보증이 가능할 수 있으며 C ++보다 더 적극적인 최적화를 허용합니다.
질문을 더 구체적으로 만들기 위해 책의 저자가 염두에 두었을 수있는 다음과 같은 제약 조건을 제안합니다.
컴파일러 설계의 맥락에서 가정 1,3,4는 코드 생성 정확성 및 / 또는 코드 최적화의 맥락에서 컴파일러 작성자의 관점에서 완벽하게 합리적이라고 생각합니다. 가정 2는 volatile 키워드가 없으면 의미가 있습니다. 그리고 이러한 가정은 제안 된 답변을 훨씬 더 명확하게 판단 할 수있을만큼 질문에 초점을 맞 춥니 다. :-)
이러한 가정을 감안할 때 const-ness를 가정 할 수없는 주요 이유는 변수 앨리어싱 때문입니다. 컴파일러는 다른 변수가 const 변수를 가리키는 지 여부를 알 수 없습니다. 별칭은 동일한 컴파일 단위에있는 다른 함수로 인해 발생할 수 있습니다.이 경우 컴파일러는 함수를 살펴보고 호출 트리를 사용하여 별칭이 발생할 수 있는지 정적으로 결정할 수 있습니다. 그러나 별칭이 라이브러리 또는 기타 외부 코드로 인한 경우 컴파일러는 변수가 별칭인지 여부를 함수 입력시 알 수 없습니다.
변수 / 인수가 const로 표시되면 앨리어싱을 통해 변경해서는 안되지만 컴파일러 작성자에게는 매우 위험하다고 주장 할 수 있습니다. 인간 프로그래머가 변수를 실제로 알기 위해 전체 시스템이나 OS 또는 라이브러리의 동작을 알지 못하는 대규모 프로젝트의 일부로 변수 const를 선언하는 것은 위험 할 수 있습니다. ' t 변경.
변수가 선언되었다고 const
해서 잘못 작성된 코드가이를 덮어 쓸 수 있다는 의미는 아닙니다.
// g++ -o foo foo.cc
#include <iostream>
void const_func(const int&a, int* b)
{
b[0] = 2;
b[1] = 2;
}
int main() {
int a = 1;
int b = 3;
std::cout << a << std::endl;
const_func(a,&b);
std::cout << a << std::endl;
}
산출:
1
2
a
하고 b
스택 변수이며, b[1]
마찬가지로 동일한 메모리 위치로 일어난다 a
.
const
모든 것이 레이블이 지정 되어 있으면 컴파일러가 무언가가 진정으로 무엇인지 알아낼 수없는 이유에 대한 OP의 원래 질문에 대한 예제 일뿐 const
입니다. 정의되지 않은 동작은 C / C ++의 일부이기 때문입니다. 나는 중단 문제 나 외부 사람의 의견보다는 그의 질문에 답할 수있는 다른 방법을 찾으려고 노력하고있었습니다.
내 의견을 확장하기 위해 그 책의 텍스트는 문제를 모호하게 만드는 것이 명확하지 않습니다.
내가 언급했듯이, 그 책은 "모든 상상할 수있는 C ++ 함수를 작성하기 위해 무한한 수의 원숭이를 확보하자. 우리가 (원숭이가 작성한 특정 함수)) 변수를 선택하는 경우가있을 것이다. 함수가 해당 변수를 변경할지 여부를 확인할 수 없습니다. "
물론 주어진 응용 프로그램의 일부 (심지어 많은) 함수의 경우 이는 컴파일러에 의해 매우 쉽게 결정될 수 있습니다. 그러나 전부는 아닙니다 (또는 반드시 대부분).
이 함수는 쉽게 분석 할 수 있습니다.
static int global;
void foo()
{
}
"foo"는 분명히 "global"을 수정하지 않습니다. 그것은 아무것도 수정하지 않으며 컴파일러는 이것을 매우 쉽게 해결할 수 있습니다.
이 함수는 그렇게 분석 할 수 없습니다.
static int global;
int foo()
{
if ((rand() % 100) > 50)
{
global = 1;
}
return 1;
"foo"의 작업은 런타임에 변경 될 수있는 값에 따라 달라 지므로 컴파일 타임에 "global"을 수정할지 여부를 결정할 수 없습니다 .
이 전체 개념은 컴퓨터 과학자가 생각하는 것보다 이해하기 훨씬 간단합니다. 함수가 런타임에 변경 될 수 있다는 점에 따라 다른 작업을 수행 할 수 있다면 실행될 때까지 수행 할 작업을 파악할 수 없으며 실행할 때마다 다른 작업을 수행 할 수 있습니다. 증명이 불가능하든 아니든, 분명히 불가능합니다.