[c++] 초기화되지 않은 지역 변수가 가장 빠른 난수 생성기입니까?

초기화되지 않은 로컬 변수가 정의되지 않은 동작 ( UB )이며 값에 트랩 표현이있을 수 있으므로 추가 작업에 영향을 줄 수 있지만 때로는 시각적 표현에만 임의의 숫자를 사용하고 다른 부분에서는 더 이상 사용하지 않으려는 경우가 있습니다. 예를 들어, 프로그램은 시각 효과에서 임의의 색상으로 무언가를 설정합니다.

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

그것보다 빠르니?

void updateEffect(){
    for(int i=0;i<1000;i++){
        star[i].setColor(rand()%255,rand()%255,rand()%255);
        star[i].setVisible(rand()%2==0?true:false);
    }
}

다른 난수 생성기보다 빠릅니다.



답변

다른 사람들이 지적했듯이 이것은 UB (Undefined Behavior)입니다.

실제로, 그것은 아마도 (실제로) 작동 할 것입니다. x86 [-64] 아키텍쳐에서 초기화되지 않은 레지스터를 읽는 것은 실제로 가비지 결과를 낳을 것이며, 아마도 레지스터가 유효하지 않은 것으로 플래그 될 수있는 Itanium과는 달리 나쁜 일을하지 않을 것입니다 .

그래도 두 가지 주요 문제가 있습니다.

  1. 특히 무작위는 아닙니다. 이 경우 스택에서 읽고 있으므로 이전에 있던 것을 얻을 수 있습니다. 10 분 전에 입력 한 비밀번호 또는 할머니의 쿠키 레시피는 사실상 임의적이며 완전히 구조화 된 것일 수 있습니다.

  2. 이와 같은 것들을 코드에 포함시키는 것은 나쁜 일 (자본 ‘B’) 입니다. 기술적으로, 컴파일러 reformat_hdd();는 정의되지 않은 변수를 읽을 때마다 삽입 할 수 있습니다 . 그것은 하지 않습니다 ,하지만 당신은 어쨌든 그것을 할 것이다. 안전하지 않은 일을하지 마십시오. 당신이 만드는 적을 예외는 안전 당신은 우발적 인 실수 있습니다 모든 시간.

UB의 더 시급한 문제는 전체 프로그램의 동작이 정의되지 않는다는 것입니다. 최신 컴파일러는이 기능을 사용하여 엄청난 양의 코드를 제거하거나 시간을 거슬러 올라갈 수 있습니다 . UB와 함께 노는 것은 살아있는 원자로를 해체하는 빅토리아 엔지니어와 같습니다. 심각한 문제가 발생할 수 있으며 기본 원칙 또는 구현 된 기술의 절반을 모를 것입니다. 그것은 수도 괜찮을,하지만 당신은 여전히이 일어나게해서는 안된다. 자세한 내용은 다른 멋진 답변을보십시오.

또한, 나는 당신을 해고합니다.


답변

이를 명확하게 말하겠습니다 : 프로그램에서 정의되지 않은 동작을 호출하지 않습니다 . 결코 좋은 생각, 기간이 아닙니다. 이 규칙에는 예외가 있습니다. 예를 들어, offsetof 구현 하는 라이브러리 구현자인 경우 . 귀하의 사례가 그러한 예외에 해당되는 경우 이미 알고있을 것입니다. 이 경우 초기화되지 않은 자동 변수를 사용하는 것은 정의되지 않은 동작 입니다.

컴파일러는 정의되지 않은 동작에 대한 최적화를 통해 매우 공격적으로 바뀌 었으며 정의되지 않은 동작으로 인해 보안 결함이 발생하는 경우가 많이 있습니다. 가장 악명 높은 경우는 아마도 C ++ 컴파일 버그에 대한 답변 에서 언급 한 Linux 커널 null 포인터 검사 제거 입니까?정의되지 않은 동작에 대한 컴파일러 최적화는 유한 루프를 무한 루프로 바꿨습니다.

CERT의 위험한 최적화 및 인과 관계의 상실 ( 비디오 )을 읽을 수 있습니다 .

점점 더 컴파일러 작성자는 C 및 C ++ 프로그래밍 언어에서 정의되지 않은 동작을 활용하여 최적화를 향상시키고 있습니다.

종종 이러한 최적화는 개발자가 소스 코드에서 원인-효과 분석을 수행하는 능력, 즉 이전 결과에 대한 다운 스트림 결과의 종속성 분석을 방해합니다.

결과적으로 이러한 최적화는 소프트웨어의 인과 관계를 제거하고 소프트웨어 결함, 결함 및 취약점의 가능성을 높입니다.

특히 불확실한 값과 관련하여 C 표준 결함 보고서 451 : 초기화되지 않은 자동 변수의 불안정성으로 인해 일부 흥미로운 판독이 가능합니다. 아직 해결되지 않았지만 흔들리는 값 의 개념을 소개 합니다. 이는 의 결정 성이 프로그램을 통해 전파 될 수 있으며 프로그램의 다른 지점에서 다른 불확실한 값을 가질 수 있음을 의미합니다.

이런 일이 발생하는 예는 모르지만 지금은 배제 할 수 없습니다.

실제 결과, 예상 결과가 아님

임의의 값을 얻지 못할 수 있습니다. 컴파일러는 루프를 완전히 최적화 할 수 있습니다. 예를 들어,이 간단한 경우

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;
        arr[i] = r ;
    }
}

clang은 그것을 멀리 최적화합니다 ( 살펴보십시오 ) :

updateEffect(int*):                     # @updateEffect(int*)
    retq

또는이 수정 된 경우와 같이 모두 0을 얻습니다.

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;
        arr[i] = r%255 ;
    }
}

라이브보기 :

updateEffect(int*):                     # @updateEffect(int*)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 64(%rdi)
    movups  %xmm0, 48(%rdi)
    movups  %xmm0, 32(%rdi)
    movups  %xmm0, 16(%rdi)
    movups  %xmm0, (%rdi)
    retq

이 두 경우 모두 완벽하게 수용 가능한 형태의 정의되지 않은 동작입니다.

우리가 Itanium에 있다면 트랩 값으로 끝날 수 있습니다 .

[…] 레지스터가 특별한 값이 아닌 값을 보유하는 경우, 몇 가지 명령을 제외하고 레지스터 트랩을 읽는다 […]

다른 중요한 메모

UB Canaries 프로젝트에 언급 된 gcc와 clang차이 가 초기화되지 않은 메모리와 관련하여 정의되지 않은 동작을 어떻게 활용할 것인지에 대해 주목 하는 것이 흥미 롭습니다 . 기사 노트 ( 강조 광산 ) :

물론 우리는 그러한 기대는, 특정 컴파일러가해야 할 일이 무엇을 함께 할 수있는 언어 표준과는 아무런 모든 것을이 없다고 스스로 완전히 명확하게 할 필요가 컴파일러의 제공이 내키지 때문에 하나 악용하는 것을 UB 하거나 그들이 아직 악용하지 않았기 때문 입니다. 컴파일러 제공 업체의 실제 보증이 존재 하지 않는 한 아직 개발되지 않은 UB는 시한 폭탄이라고 말하고 싶습니다 .

Matthieu M.은 모든 C 프로그래머가 정의되지 않은 행동 # 2 / 3에 대해 알아야 할 사항도 지적합니다 . 그것은 다른 것들 중에서도 강조합니다 (내 것을 강조합니다 ).

실현하기 위해 중요하고 무서운 일이 있다는 것입니다 단지에 대한 어떤
정의되지 않은 동작을 기반으로 최적화 미래에 언제든지 버그 코드를 트리거하고 시작할 수 있습니다
. 인라인, 루프 언 롤링, 메모리 승격 및 기타 최적화는 계속 향상되고 있으며 기존 이유의 상당 부분은 위와 같은 2 차 최적화를 제공하는 것입니다.

나에게 이것은 컴파일러가 필연적으로 비난을 받기 때문에 부분적으로 불만족 스러우며 또한 C 코드의 거대한 몸체 가 폭발하기를 기다리는 지뢰 라는 것을 의미하기 때문 입니다.

완전성을 위해서 아마 구현 예를 들어, 잘 정의 된 정의되지 않은 동작을 선택할 수 있습니다 언급해야 GCC가 노동 조합을 통해 유형 말장난을 허용 하면서 C ++에서이 정의되지 않은 동작처럼 보인다 . 이 경우 구현시 문서화해야하며 일반적으로 이식성이 없습니다.


답변

아뇨, 끔찍 해요

초기화되지 않은 변수를 사용하는 동작은 C 및 C ++에서 정의되지 않으며 이러한 체계가 바람직한 통계적 속성을 가질 가능성은 거의 없습니다.

“빠르고 더러운”난수 생성기를 원한다면 rand()최선의 선택입니다. 구현시 곱셈, 덧셈 및 모듈러스 만 수행됩니다.

내가 아는 가장 빠른 발생기는을 사용하도록 요구 uint32_t하여 의사 랜덤 변수의 유형으로 I, 사용

I = 1664525 * I + 1013904223

연속적인 값을 생성합니다. 당신의 초기 값을 선택할 수 있습니다 I(호출 당신의 공상 소요). 분명히 인라인으로 코딩 할 수 있습니다. 부호없는 유형의 표준 보증 랩 어라운드는 모듈러스 역할을합니다. (숫자 상수는 저명한 과학 프로그래머 인 Donald Knuth가 직접 선택합니다.)


답변

좋은 질문!

정의되지 않은 것이 무작위임을 의미하지는 않습니다. 초기화되지 않은 전역 변수에서 얻을 수있는 값은 시스템이나 다른 응용 프로그램이 실행 중이었던 것으로 생각하십시오. 더 이상 사용하지 않는 메모리로 시스템이 수행하는 작업 및 / 또는 시스템 및 응용 프로그램이 생성하는 값의 종류에 따라 다음을 얻을 수 있습니다.

  1. 항상 동일합니다.
  2. 작은 값 집합 중 하나입니다.
  3. 하나 이상의 작은 범위에서 값을 가져옵니다.
  4. 16/32/64 비트 시스템의 포인터에서 2/4/8로 나눌 수있는 많은 값보기

얻을 수있는 값은 시스템 및 / 또는 응용 프로그램에 의해 임의의 비임의 값이 남는 것에 따라 달라집니다. 따라서 실제로 시스템에서 더 이상 사용 된 메모리를 지우지 않는 한 약간의 노이즈가 발생하지만 그릴 값 풀은 무작위가 아닙니다.

지역 변수는 자신의 프로그램 스택에서 직접 가져 오기 때문에 상황이 훨씬 나빠집니다. 다른 코드를 실행하는 동안 프로그램이 실제로 이러한 스택 위치를 작성할 가능성이 매우 높습니다. 나는이 상황에서 운이 매우 낮을 것으로 추정하고, 당신이 만드는 ‘무작위’코드 변경은이 운을 시도합니다.

무작위성 에 대해 읽으십시오 . 보시다시피 무작위성은 매우 구체적이며 구하기 어려운 속성입니다. (추천과 같이) 추적하기 어려운 것을 취하면 임의의 가치를 얻는다고 생각하는 것은 흔한 실수입니다.


답변

많은 좋은 답변이지만 다른 것을 추가하고 결정 론적 컴퓨터에서는 무작위가 없다는 점을 강조 할 수 있습니다. 이는 의사 -RNG에 의해 생성 된 숫자와 스택의 C / C ++ 로컬 변수 용으로 예약 된 메모리 영역에서 발견 된 “임의”숫자에 해당됩니다.

그러나 … 결정적인 차이가 있습니다.

좋은 의사 난수 생성기에 의해 생성 된 숫자는 통계를 무작위로 그리는 것과 통계적으로 유사하게하는 속성을 갖습니다. 예를 들어 분포가 균일합니다. 사이클 길이가 길다 : 사이클이 반복되기 전에 수백만 개의 난수를 얻을 수 있습니다. 시퀀스는 자동 상관되지 않습니다. 예를 들어, 2, 3, 27 번째 숫자를 취하거나 생성 된 숫자의 특정 숫자를 보면 이상한 패턴이 나타나지 않습니다.

반대로, 스택에 남겨진 “임의”숫자에는 이러한 속성이 없습니다. 이들의 값과 명백한 임의성은 프로그램 구성 방법, 컴파일 방법 및 컴파일러가 최적화하는 방법에 전적으로 의존합니다. 예를 들어, 다음은 독립형 프로그램으로서의 아이디어 변형입니다.

#include <stdio.h>

notrandom()
{
        int r, g, b;

        printf("R=%d, G=%d, B=%d", r&255, g&255, b&255);
}

int main(int argc, char *argv[])
{
        int i;
        for (i = 0; i < 10; i++)
        {
                notrandom();
                printf("\n");
        }

        return 0;
}

Linux 컴퓨터에서 GCC 로이 코드를 컴파일하고 실행하면 다소 불쾌하게 결정됩니다.

R=0, G=19, B=0
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255

디스어셈블러로 컴파일 된 코드를 살펴보면 진행중인 작업을 자세히 재구성 할 수 있습니다. notrandom ()에 대한 첫 번째 호출은 이전에이 프로그램에서 사용되지 않은 스택 영역을 사용했습니다. 거기에 무엇이 있는지 아는 사람. 그러나 notrandom ()에 대한 호출 후에 printf ()에 대한 호출이 있습니다 (GCC 컴파일러는 실제로 putchar ()에 대한 호출에 대해 최적화하지만 결코 신경 쓰지 않습니다). 그러면 스택을 덮어 씁니다. 따라서 다음에 notrandom ()이 호출되면 스택은 putchar () 실행의 오래된 데이터를 포함하며 putchar ()은 항상 동일한 인수로 호출되므로이 오래된 데이터는 항상 동일합니다. 너무.

따라서이 동작에 대해 임의의 것은 없으며,이 방법으로 얻은 숫자는 잘 작성된 의사 난수 생성기의 바람직한 특성을 갖지 않습니다. 실제로 대부분의 실제 시나리오에서 그 값은 반복적이고 상관성이 높습니다.

사실, 다른 사람들과 마찬가지로, 나는이 아이디어를 “고성능 RNG”로 전달하려는 누군가를 해고 할 것을 진지하게 고려할 것입니다.


답변

정의되지 않은 동작은 프로그래머가 어떤 일이 있어도 불만을 제기 할 권리가 없기 때문에 컴파일러 작성자가 문제를 무시할 수 있음을 의미합니다.

이론적으로 UB 땅에 들어갈 때 어떤 일이 발생할 수 있습니까 ( 코를 날리는 데몬 포함 ) 일반적으로 컴파일러 작성자는 신경 쓰지 않으며 로컬 변수의 경우 값은 해당 시점의 스택 메모리에 있습니다 .

이것은 종종 내용이 “이상한”것이지만 고정되거나 약간 임의적이거나 가변적이지만 분명한 패턴을 가지고 있음을 의미합니다 (예 : 각 반복에서 값이 증가 함).

확실히 괜찮은 무작위 생성기가 될 수는 없습니다 .


답변

정의되지 않은 동작은 정의되어 있지 않습니다. 그것은 당신이 정의되지 않은 값을 얻는다는 것을 의미하지는 않습니다. 그것은 프로그램이 무엇이든 있고 여전히 언어 사양을 충족 한다는 것을 의미합니다 .

좋은 최적화 컴파일러는

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

이것을 noop로 컴파일하십시오. 이것은 다른 대안보다 확실히 빠릅니다. 그것은 아무것도하지 않을 것이라는 단점이 있지만, 정의되지 않은 행동의 단점입니다.