지역 변수에 초기화가 필요한 이유는 무엇입니까?


140

내 수업 내에서 부울을 만들면과 같은 bool check것이 기본값은 false입니다.

bool check클래스 내에서가 아닌 내 메서드 내에서 동일한 부울을 만들면 "할당되지 않은 로컬 변수 검사 사용"오류가 발생합니다. 왜?


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Martijn Pieters

14
문제는 모호하다. "사양이 그렇게 말했기 때문에"수용 가능한 대답일까요?
Eric Lippert 2016 년

4
그것이 그들이 복사 할 때 Java에서 수행 된 방식이기 때문입니다. : P
Alvin Thompson

답변:


177

유발과 다윗의 대답은 기본적으로 정확합니다. 합산:

  • 할당되지 않은 지역 변수를 사용하는 것은 버그 일 가능성이 높으며, 이는 컴파일러가 저렴한 비용으로 감지 할 수 있습니다.
  • 할당되지 않은 필드 또는 배열 요소를 사용하면 버그가 발생할 가능성이 적으며 컴파일러에서 조건을 감지하기가 더 어렵습니다. 따라서 컴파일러는 필드에 초기화되지 않은 변수의 사용을 감지하려고 시도하지 않고 프로그램 동작을 결정 론적으로 만들기 위해 기본값에 대한 초기화에 의존합니다.

David의 답변에 대한 의견자는 정적 분석을 통해 할당되지 않은 필드의 사용을 감지 할 수없는 이유를 묻습니다. 이것이이 답변에서 확장하고 싶은 요점입니다.

먼저, 로컬 또는 다른 변수의 경우 실제로 변수가 할당되었는지 또는 할당되지 않았는지 정확하게 판단하는 것은 불가능합니다 . 치다:

bool x;
if (M()) x = true;
Console.WriteLine(x);

"x가 할당 되었습니까?"라는 질문 "M ()이 true를 반환합니까?"와 같습니다. 이제 Fermat의 Last Theorem이 eleventy gajillion보다 작은 모든 정수에 대해 true이면 M ()이 true를 리턴하고 그렇지 않으면 false를 리턴한다고 가정하십시오. x가 확실히 할당되었는지 확인하려면 컴파일러는 본질적으로 Fermat의 마지막 정리 증명을 생성해야합니다. 컴파일러는 그렇게 똑똑하지 않습니다.

그래서 컴파일러는 지역 주민 대신하는 일은 구현하는 것입니다 알고리즘 신속 하고 과대 평가 지역이 확실히 할당되지 않은 경우. 즉, 그것은 당신과 내가 알고 있음에도 불구하고 "이 지역이 할당되었음을 증명할 수 없습니다"라고 말하는 오 탐지를 가지고 있습니다. 예를 들면 다음과 같습니다.

bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);

N ()이 정수를 리턴한다고 가정하십시오. 당신과 나는 N () * 0이 0이라는 것을 알고 있지만 컴파일러는 그것을 모른다. (참고 : C # 2.0 컴파일러 는 그것을 알고 있었지만 사양 에서 컴파일러가 알고 있다고 말하지 않기 때문에 최적화를 제거 했습니다.)

좋아요, 지금까지 우리는 무엇을 알고 있습니까? 현지인이 정확한 답변을 얻는 것은 비현실적이지만, 할당되지 않은 비용을 싸게 과대 평가하고 "불명확 한 프로그램을 수정하게"하는 측면에서 잘못된 결과를 얻을 수 있습니다. 잘 됐네요 필드에 대해 동일한 작업을 수행하지 않는 이유는 무엇입니까? 즉, 값을 과대 평가하는 명확한 할당 검사기를 만드는가?

글쎄, 로컬을 초기화하는 방법은 몇 가지입니까? 메소드의 텍스트 내에서 지정할 수 있습니다. 메소드 텍스트에서 람다 내에 할당 될 수 있습니다. 해당 람다는 호출되지 않을 수 있으므로 이러한 할당은 관련이 없습니다. 또는 메소드를 "out"으로 전달하여 메소드가 정상적으로 리턴 될 때 지정되었다고 가정 할 수 있습니다. 이것들은 로컬이 할당되는 매우 명확한 지점이며 로컬이 선언 된 것과 동일한 방법으로 바로 거기에 있습니다. 현지인에 대한 명확한 할당을 결정하려면 현지 분석 만 필요합니다 . 분석법은 분석법에서 백만 줄 미만의 코드보다 훨씬 짧은 경향이 있으므로 전체 분석법을 분석하는 것은 매우 빠릅니다.

이제 필드는 어떻습니까? 물론 생성자에서 필드를 초기화 할 수 있습니다. 또는 필드 이니셜 라이저. 또는 생성자가 필드를 초기화하는 인스턴스 메소드를 호출 할 수 있습니다. 또는 생성자가 필드를 구체화하지 않는 가상 메소드를 호출 할 수 있습니다. 또는 생성자가 필드를 초기화하는 다른 클래스 ( 라이브러리에 있을 수 있음) 에서 메소드 호출 할 수 있습니다 . 정적 필드는 정적 생성자에서 초기화 될 수 있습니다. 정적 필드는 다른 정적 생성자에 의해 초기화 될 수 있습니다 .

본질적으로 필드의 이니셜 라이저는 아직 작성되지 않은 라이브러리에서 선언 될 가상 메소드 내부를 포함 하여 전체 프로그램의 어느 곳에 나 있을 수 있습니다 .

// Library written by BarCorp
public abstract class Bar
{
    // Derived class is responsible for initializing x.
    protected int x;
    protected abstract void InitializeX(); 
    public void M() 
    { 
       InitializeX();
       Console.WriteLine(x); 
    }
}

이 라이브러리를 컴파일하는 것은 오류입니까? 그렇다면 BarCorp는 어떻게 버그를 해결해야합니까? x에 기본값을 할당하여? 그러나 이것이 컴파일러가 이미하는 일입니다.

이 라이브러리가 합법적이라고 가정하십시오. FooCorp가 쓰는 경우

public class Foo : Bar
{
    protected override void InitializeX() { } 
}

입니다 오류? 컴파일러는 어떻게 알아낼까요? 유일한 방법은 런타임에 가상 메소드의 선택을 포함하는 경로를 포함 하여 프로그램을 통해 가능한 모든 경로 에서 모든 필드 의 초기화 정적을 추적 하는 전체 프로그램 분석 을 수행하는 입니다. 이 문제는 임의로 어려울 수 있습니다 . 수백만 개의 제어 경로를 시뮬레이션하여 실행할 수 있습니다. 로컬 제어 흐름을 분석하는 데는 마이크로 초가 걸리며 분석법의 크기에 따라 다릅니다. 전역 제어 흐름을 분석하는 데는 프로그램과 모든 라이브러리모든 방법 이 복잡하기 때문에 시간이 걸릴 수 있습니다 .

그렇다면 전체 프로그램을 분석 할 필요가없고 훨씬 더 심하게 과대 평가하는 저렴한 분석을 수행해보십시오. 실제로 컴파일하는 올바른 프로그램을 작성하기가 어렵지 않은 알고리즘을 제안하면 디자인 팀이이를 고려할 수 있습니다. 나는 그런 알고리즘을 모른다.

이제 주석 작성자는 "생성자가 모든 필드를 초기화해야합니다"라고 제안합니다. 나쁜 생각이 아닙니다. 실제로 C #에 이미 structs 기능이 있다는 것은 나쁘지 않은 생각입니다 . ctor가 정상적으로 반환 할 때까지 모든 필드를 확실히 할당하려면 구조체 생성자가 필요합니다. 기본 생성자는 모든 필드를 기본값으로 초기화합니다.

수업은 어떻습니까? 음, 생성자가 필드를 초기화했다는 것을 어떻게 알 수 있습니까? ctor는 가상 메소드 를 호출 하여 필드를 초기화 할 수 있었고 이제는 이전과 같은 위치로 돌아 왔습니다. Structs에는 파생 클래스가 없습니다. 수업이 가능합니다. 모든 필드를 초기화하는 생성자를 포함하려면 추상 클래스가 포함 된 라이브러리가 필요합니까? 추상 클래스는 필드를 초기화 해야하는 값을 어떻게 알 수 있습니까?

John은 필드가 초기화되기 전에 ctor에서 호출 메소드를 금지하는 것을 제안합니다. 요약하자면, 우리의 옵션은 다음과 같습니다.

  • 일반적이고 안전하며 자주 사용되는 프로그래밍 관용구를 불법으로 만듭니다.
  • 고가의 전체 프로그램 분석을 수행하여 컴파일되지 않은 버그를 찾기 위해 컴파일에 몇 시간이 걸립니다.
  • 기본값으로 자동 초기화합니다.

디자인 팀은 세 번째 옵션을 선택했습니다.


1
평소와 같이 좋은 대답. 그래도 질문이 있습니다 : 왜 자동으로 로컬 변수에 기본값을 할당하지 않습니까? 다시 말해, 메소드 내부에서도bool x; 동등 하지 않습니까? bool x = false;
durron597 2016 년

8
@ durron597 : 경험에 따르면 로컬에 값을 할당하는 것을 잊어 버리는 것은 아마도 버그 일 것입니다. 그 아마 버그 경우 는 저렴하고 감지하기 쉽고, 다음 불법 또는 경고 중 하나의 행동을 좋은 인센티브가있다.
Eric Lippert

27

내 클래스 내에서 대신 bool check 내 메소드 내에서 동일한 bool을 만들면 "할당되지 않은 로컬 변수 검사 사용"오류가 발생합니다. 왜?

컴파일러가 실수를 저 지르려고하지 않기 때문입니다.

false이 특정 실행 경로에서 변경 하기 위해 변수를 초기화합니까 ? default(bool)어쨌든 , 고려하는 것은 어쨌든 거짓이지만, 이것이 일어나고 있음인식 하도록 강요합니다 . .NET 환경에서는 값을 기본값으로 초기화하므로 "쓰레기 메모리"에 액세스 할 수 없습니다. 그러나 여전히 이것이 참조 유형이라고 가정하면 null이 아닌 것으로 예상되는 메소드에 초기화되지 않은 (널) 값을 전달하고 런타임에 NRE를 얻습니다. 컴파일러는이를 막기 위해 노력하고 있으며, 이로 인해 때때로 bool b = false명령문 이 발생할 수도 있다는 사실을 인정합니다 .

Eric Lippert 는 블로그 게시물에서 이에 대해 이야기합니다 .

우리가 이것을 불법으로 만들고 싶은 이유는 많은 사람들이 믿는 것처럼 지역 변수가 쓰레기로 초기화되고 쓰레기로부터 당신을 보호하기를 원하기 때문입니다. 실제로 자동으로 로컬을 기본값으로 초기화합니다. (C 및 C ++ 프로그래밍 언어는 초기화되지 않은 로컬에서 가비지를 읽을 수 없으며 기꺼이 읽을 수 있습니다.) 오히려 그러한 코드 경로의 존재는 버그 일 가능성이 높기 때문에 발생합니다. 품질의 구덩이; 그 버그를 쓰려면 열심히 노력해야합니다.

왜 이것이 수업 분야에 적용되지 않습니까? 글쎄, 나는 선을 어딘가에 그려야한다고 가정하고 클래스 변수와 달리 로컬 변수 초기화는 진단하고 올바르게 얻는 것이 훨씬 쉽습니다. 컴파일러 는이 작업을 수행 할 수 있지만 클래스의 각 필드가 초기화되었는지 평가하기 위해 수행해야하는 모든 가능한 검사 (일부 클래스는 클래스 코드 자체와 무관)를 생각하십시오. 나는 컴파일러 디자이너는 아니지만 많은 경우가 고려되고 적시에 수행되어야 하기 때문에 확실히 어려울 것이라고 확신합니다 . 모든 기능에 대해 설계, 작성, 테스트 및 배포해야하는 노력과 달리 구현의 가치는 가치가없고 복잡합니다.


"이 유형이 참조 유형이라고 가정하고 초기화되지 않은 객체를 초기화 된 유형을 예상하는 메소드에 전달할 것"이라고 의미 했습니까? "이 유형이 참조 유형이라고 가정하고 목적"?
중복 제거기

@ 중복 제거기 예. 널이 아닌 값을 예상하는 메소드. 해당 부분을 편집했습니다. 더 명확 해지기를 바랍니다.
Yuval Itzchakov 2016 년

나는 그것이 그려진 선 때문이라고 생각하지 않습니다. 모든 클래스는 최소한 기본 생성자 인 생성자를 가지고 있다고 가정합니다. 따라서 기본 생성자를 고수하면 기본값이 나타납니다 (조용함). 생성자를 정의 할 때 생성자 내에서 수행중인 작업과 기본값에 대한 지식을 포함하여 어떤 방식으로 초기화하려는 필드를 알고 있어야합니다.
Peter

반대로 : 메소드 내의 필드는 선언되고 할당 된 값으로 다른 실행 경로에있을 수 있습니다. 사용하는 프레임 워크의 문서 나 유지 관리 할 수없는 코드의 다른 부분을 볼 때까지 쉽게 감독 할 수있는 예외가있을 수 있습니다. 이것은 매우 복잡한 실행 경로를 도입 할 수 있습니다. 따라서 컴파일러는 힌트를줍니다.
피터

@ 피터 당신의 두 번째 의견을 이해하지 못했습니다. 첫 번째와 관련하여 생성자 내에서 필드를 초기화 할 필요는 없습니다. 일반적인 관행 입니다. 컴파일러 작업은 그러한 관행을 강요하지 않습니다. 실행중인 생성자의 구현에 의존 할 수 없으며 "좋아, 모든 필드를 사용하는 것이 좋습니다"라고 말합니다. Eric은 클래스의 필드를 초기화 할 수있는 방법에 대한 답변을 자세히 설명했으며 모든 논리적 방법 초기화를 계산하는 데 시간 이 얼마나 오래 걸리는지 보여줍니다 .
Yuval Itzchakov

25

지역 변수에 초기화가 필요한 이유는 무엇입니까?

짧은 대답은 초기화되지 않은 로컬 변수에 액세스하는 코드가 정적 분석을 사용하여 안정적인 방식으로 컴파일러에 의해 감지 될 수 있다는 것입니다. 필드의 경우는 그렇지 않습니다. 따라서 컴파일러는 첫 번째 경우를 시행하지만 두 번째 경우는 시행하지 않습니다.

지역 변수에 초기화가 필요한 이유는 무엇입니까?

Eric Lippert가 설명했듯이 이는 C # 언어의 디자인 결정에 지나지 않습니다 . CLR 및 .NET 환경에는 필요하지 않습니다. 예를 들어 VB.NET은 초기화되지 않은 로컬 변수를 사용하여 제대로 컴파일되며 실제로 CLR은 초기화되지 않은 모든 변수를 기본값으로 초기화합니다.

C #에서도 마찬가지 일 수 있지만 언어 디자이너는 그렇지 않았습니다. 그 이유는 초기화 된 변수가 버그의 큰 원인이므로 초기화를 강제함으로써 컴파일러는 실수로 실수를 줄일 수 있도록 도와줍니다.

왜 필드에 초기화가 필요하지 않습니까?

그렇다면 왜 강제 명시 적 초기화가 클래스 내의 필드에서 발생하지 않습니까? 생성 중에, 객체 이니셜 라이저에 의해 호출되는 속성을 통해, 또는 이벤트 후에 오랫동안 호출되는 메서드를 통해 명시적인 초기화가 발생할 수 있기 때문입니다. 컴파일러는 정적 분석을 사용하여 코드를 통해 가능한 모든 경로가 변수가 명시 적으로 초기화되기 전에 확인할 수 없습니다. 개발자가 컴파일되지 않는 유효한 코드를 남길 수 있기 때문에 잘못하면 성 가실 것입니다. 따라서 C #은 전혀 적용하지 않으며 CLR은 명시 적으로 설정되지 않은 경우 필드를 기본값으로 자동 초기화합니다.

컬렉션 유형은 어떻습니까?

로컬 변수 초기화에 대한 C #의 시행은 제한되어 있으며 종종 개발자를 사로 잡습니다. 다음 네 줄의 코드를 고려하십시오.

string str;
var len1 = str.Length;
var array = new string[10];
var len2 = array[0].Length;

코드의 두 번째 줄은 초기화되지 않은 문자열 변수를 읽으려고하기 때문에 컴파일되지 않습니다. 코드의 네 번째 줄은 array초기화되었지만 기본값으로 만 잘 컴파일 됩니다. 문자열의 기본값은 null이므로 런타임에 예외가 발생합니다. 여기에서 스택 오버플로에 시간을 보낸 사람은이 명시 적 / 암시 적 초기화 불일치로 인해 ""객체 참조가 객체의 인스턴스로 설정되지 않았습니다 "라는 오류가 발생하는 이유는 무엇입니까?" 질문.


"컴파일러는 정적 분석을 사용하여 코드를 통해 가능한 모든 경로가 변수가 명시 적으로 초기화되기 전에 결정할 수 없습니다." 이것이 사실이라고 확신하지 않습니다. 정적 분석에 내성이있는 프로그램의 예를 게시 할 수 있습니까?
John Kugelman

@JohnKugelman은 간단한 사례 public interface I1 { string str {get;set;} }와 방법을 고려하십시오 int f(I1 value) { return value.str.Length; }. 이 라이브러리에있는 경우, 컴파일러는이 따라서인지, 그 라이브러리에 연결됩니다 알 수없는 set전과 호출 된 것 get, 백킹 필드가 명시 적으로 초기화되지 않을 수 있지만, 이러한 코드를 컴파일 할 수있다.
David Arno

사실이지만 컴파일하는 동안 오류가 발생하지 않을 것 f입니다. 생성자를 컴파일 할 때 생성됩니다. 초기화되지 않은 필드를 가진 생성자를 남겨두면 오류가 발생합니다. 모든 필드가 초기화되기 전에 클래스 메소드 및 게터 호출에 대한 제한 사항이있을 수도 있습니다.
John Kugelman 2016 년

@ JohnKugelman : 제기 한 문제에 대한 답변을 게시하겠습니다.
Eric Lippert

4
그것은 공평하지 않다. 우리는 여기에 의견이 일치하지 않습니다!
John Kugelman 2016 년

10

위의 좋은 답변이지만, 사람들이 긴 책을 읽기 위해 게 으르도록 훨씬 간단하고 짧은 답변을 게시한다고 생각했습니다.

수업

class Foo {
    private string Boo;
    public Foo() { /** bla bla bla **/ }
    public string DoSomething() { return Boo; }
}

생성자에서 속성 이 초기화되었거나 초기화 되지 않았을 Boo수 있습니다 . 따라서 발견되면 초기화 되었다고 가정 하지 않습니다 . 단순히 오류 를 억제 합니다.return Boo;

함수

public string Foo() {
   string Boo;
   return Boo; // triggers error
}

{ }문자는 코드 블록의 범위를 정의합니다. 컴파일러는 이러한 { }블록 의 분기를 추적하여 내용을 추적합니다. 초기화되지 않았다는 것을 쉽게 알 수 있습니다Boo . 그런 다음 오류가 발생합니다.

왜 오류가 있습니까?

소스 코드를 안전하게 만드는 데 필요한 코드 줄 수를 줄이기 위해 오류가 도입되었습니다. 오류가 없으면 위와 같이 보입니다.

public string Foo() {
   string Boo;
   /* bla bla bla */
   if(Boo == null) {
      return "";
   }
   return Boo;
}

매뉴얼에서 :

C # 컴파일러는 초기화되지 않은 변수의 사용을 허용하지 않습니다. 컴파일러에서 초기화되지 않은 변수의 사용을 감지하면 컴파일러 오류 CS0165가 생성됩니다. 자세한 내용은 필드 (C # 프로그래밍 안내서)를 참조하십시오. 이 오류는 컴파일러에서 특정 코드가없는 경우에도 할당되지 않은 변수를 사용할 수있는 구문을 발견하면 생성됩니다. 이를 통해 명확한 할당을 위해 지나치게 복잡한 규칙이 필요하지 않습니다.

참조 : https://msdn.microsoft.com/en-us/library/4y7h161d.aspx

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