A variável local não inicializada é o gerador de número random mais rápido?

Eu sei que a variável local não inicializada é o comportamento indefinido ( UB ), e também o valor pode ter representações de armadilha que podem afetar outras operações, mas às vezes eu quero usar o número random somente para representação visual e não as utilizarei em outra parte programa, por exemplo, definir algo com cor aleatória em um efeito visual, por exemplo:

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); } } 

é mais rápido que

 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); } } 

e também mais rápido que outro gerador de números randoms?

Como outros notaram, isso é comportamento indefinido (UB).

Na prática, isso irá (provavelmente) realmente funcionar. A leitura de um registro não inicializado em arquiteturas x86 [-64] produzirá resultados ruins, e provavelmente não fará nada de errado (por exemplo, em Itanium, onde os registros podem ser marcados como inválidos , de modo que lê erros de propagação como NaN).

Existem dois problemas principais:

  1. Não será particularmente random. Neste caso, você está lendo da pilha, então você terá o que havia antes. O que pode ser efetivamente random, completamente estruturado, a senha que você digitou dez minutos atrás, ou a receita de cookies da sua avó.

  2. É uma prática Ruim (maiúscula ‘B’) deixar que coisas assim se insinuem no seu código. Tecnicamente, o compilador poderia inserir reformat_hdd(); toda vez que você lê uma variável indefinida. Não vai , mas você não deveria fazer isso de qualquer maneira. Não faça coisas inseguras. Quanto menos exceções você fizer, mais seguro você estará com erros acidentais o tempo todo.

    O problema mais urgente com o UB é que ele deixa o comportamento de todo o seu programa indefinido. Os compiladores modernos podem usar isso para eliminar grandes faixas do seu código ou até mesmo voltar no tempo . Jogar com o UB é como um engenheiro vitoriano desmancanvasndo um reator nuclear ao vivo. Há um zilhão de coisas para dar errado e você provavelmente não conhecerá metade dos princípios subjacentes ou a tecnologia implementada. Pode estar tudo bem, mas você ainda não deve deixar isso acontecer. Veja as outras boas respostas para detalhes.

Além disso, eu te demitiria.

Deixe-me dizer isso claramente: não invocamos comportamento indefinido em nossos programas . Nunca é uma boa ideia, ponto final. Há raras exceções a essa regra; por exemplo, se você é um implementador de bibliotecas implementando offsetof . Se o seu caso se enquadra nessa exceção, você provavelmente já sabe disso. Nesse caso, sabemos que usar variables ​​automáticas não inicializadas é um comportamento indefinido .

Compiladores tornaram-se muito agressivos com otimizações em torno do comportamento indefinido e podemos encontrar muitos casos em que o comportamento indefinido levou a falhas de segurança. O caso mais infame é provavelmente a remoção da verificação de ponteiro nulo do kernel Linux que menciono na minha resposta ao bug de compilation C ++? onde uma otimização de compilador em torno do comportamento indefinido transformou um loop finito em um infinito.

Podemos ler as Otimizações Perigosas do CERT e a Perda de Causalidade ( vídeo ) que diz, entre outras coisas:

Cada vez mais, os criadores de compiladores estão aproveitando os comportamentos indefinidos nas linguagens de programação C e C ++ para melhorar as otimizações.

Freqüentemente, essas otimizações estão interferindo na capacidade dos desenvolvedores de realizar análises de causa e efeito em seu código-fonte, ou seja, analisando a dependência dos resultados posteriores em resultados anteriores.

Consequentemente, essas otimizações estão eliminando a causalidade no software e aumentando a probabilidade de falhas, defeitos e vulnerabilidades de software.

Especificamente em relação a valores indeterminados, o relatório de defeito padrão C 451: Instabilidade de variables ​​automáticas não inicializadas faz uma leitura interessante. Ainda não foi resolvido, mas introduz o conceito de valores instáveis, o que significa que a indeterminação de um valor pode se propagar através do programa e pode ter diferentes valores indeterminados em diferentes pontos do programa.

Não conheço nenhum exemplo em que isso acontece, mas a essa altura não podemos descartar.

Exemplos reais, não o resultado que você espera

É improvável que você obtenha valores randoms. Um compilador pode otimizar completamente o loop. Por exemplo, com este caso simplificado:

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

O clang o otimiza ( veja ao vivo ):

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

ou talvez obtenha todos os zeros, como neste caso modificado:

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

veja ao vivo :

 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 

Ambos os casos são formas perfeitamente aceitáveis ​​de comportamento indefinido.

Note que, se estivermos em um Itanium, poderemos acabar com um valor de trap :

[...] se o registro tiver um valor especial de não-coisa, ler as armadilhas de registro, exceto algumas instruções [...]

Outras notas importantes

É interessante observar a variação entre o gcc e o clang observado no projeto UB Canaries sobre o quanto eles estão dispostos a aproveitar o comportamento indefinido em relação à memory não inicializada. O artigo observa ( ênfase minha ):

É claro que precisamos ter certeza absoluta de que qualquer expectativa desse tipo não tem nada a ver com o padrão de linguagem e tudo a ver com o que um compilador em particular faz, seja porque os provedores desse compilador não estão dispostos a explorar esse UB ou apenas porque eles ainda não conseguiram explorá-lo . Quando não existe garantia real do provedor do compilador, gostamos de dizer que os UBs ainda não explorados são bombas-relógio : eles estão esperando para sair no próximo mês ou no próximo ano, quando o compilador ficar um pouco mais agressivo.

Como Matthieu M. aponta O que todo programador C deve saber sobre comportamento indefinido # 2/3 também é relevante para esta questão. Diz entre outras coisas ( ênfase minha ):

O importante e assustador é perceber que praticamente qualquer otimização baseada em comportamento indefinido pode começar a ser acionada em código com bugs a qualquer momento no futuro . Inlining, desenrolamento de loop, promoção de memory e outras otimizações continuarão melhorando, e uma parte significativa do motivo de sua existência é expor as otimizações secundárias, como as acima.

Para mim, isso é profundamente insatisfatório, em parte porque o compilador inevitavelmente acaba sendo culpado, mas também porque significa que corpos enormes de código C são minas terrestres esperando para explodir.

Para ser completamente completo, eu provavelmente deveria mencionar que as implementações podem escolher tornar o comportamento indefinido bem definido, por exemplo, o gcc permite digitar através de uniões, enquanto em C ++ isso parece um comportamento indefinido . Se este for o caso, a implementação deve documentá-lo e isso geralmente não será portátil.

Não, é terrível.

O comportamento de usar uma variável não inicializada é indefinido em C e C ++, e é muito improvável que tal esquema tenha propriedades statistics desejáveis.

Se você quer um gerador de números randoms “rápido e sujo”, então rand() é sua melhor aposta. Em sua implementação, tudo o que faz é uma multiplicação, uma adição e um módulo.

O gerador mais rápido que eu conheço requer que você use um uint32_t como o tipo da variável pseudo-aleatória I , e use

I = 1664525 * I + 1013904223

para gerar valores sucessivos. Você pode escolher qualquer valor inicial de I (chamado de semente ) que lhe agrade. Obviamente, você pode codificar isso inline. O enrolamento padrão garantido de um tipo não assinado atua como o módulo. (As constantes numéricas são escolhidas a dedo pelo notável programador científico Donald Knuth.)

Boa pergunta!

Indefinido não significa que seja random. Pense nisso, os valores que você obteria em variables ​​globais não inicializadas foram deixados lá pelo sistema ou seus / outros aplicativos em execução. Dependendo do que seu sistema faz com a memory não usada e / ou que tipo de valores o sistema e os aplicativos geram, você pode obter:

  1. Sempre o mesmo.
  2. Seja um de um pequeno conjunto de valores.
  3. Obtenha valores em um ou mais intervalos pequenos.
  4. Veja muitos valores divididos por 2/4/8 de pointers no sistema 16/32/64 bits

Os valores que você obterá dependem completamente de quais valores não randoms são deixados pelo sistema e / ou pelos aplicativos. Então, de fato, haverá algum ruído (a menos que seu sistema não consuma mais a memory usada), mas o conjunto de valores do qual você desenhará não será random.

As coisas ficam muito piores para as variables ​​locais porque elas vêm diretamente da pilha do seu próprio programa. Existe uma boa chance de que seu programa realmente escreva esses locais de pilha durante a execução de outro código. Eu estimo as chances de sorte nessa situação muito baixas, e uma mudança de código “aleatória” que você faz tenta essa sorte.

Leia sobre aleatoriedade . Como você verá, a aleatoriedade é muito específica e difícil de obter. É um erro comum pensar que, se você pegar algo difícil de rastrear (como sua sugestão), obterá um valor random.

Muitas boas respostas, mas permitam-me adicionar outra e enfatizar o ponto de que em um computador determinista, nada é random. Isso é verdade para os números produzidos por um pseudo-RNG e os números aparentemente “randoms” encontrados em áreas de memory reservadas para variables ​​locais C / C ++ na pilha.

MAS … há uma diferença crucial.

Os números gerados por um bom gerador pseudo-random têm as propriedades que os tornam estatisticamente semelhantes aos empates verdadeiramente randoms. Por exemplo, a distribuição é uniforme. A duração do ciclo é longa: você pode obter milhões de números randoms antes que o ciclo se repita. A sequência não é autocorrelacionada: por exemplo, você não começará a ver padrões estranhos surgindo se você pegar cada segundo, terceiro ou número 27, ou se você olhar para dígitos específicos nos números gerados.

Em contraste, os números “randoms” deixados na pilha não possuem nenhuma dessas propriedades. Seus valores e sua aleatoriedade aparente dependem inteiramente de como o programa é construído, como é compilado e como é otimizado pelo compilador. Por exemplo, aqui está uma variação da sua ideia como um programa independente:

 #include  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; } 

Quando eu compilo este código com o GCC em uma máquina Linux e o executo, ele acaba sendo desagradavelmente determinístico:

 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 

Se você olhasse o código compilado com um desmontador, você poderia reconstruir o que estava acontecendo, em detalhes. A primeira chamada para notrandom () usou uma área da pilha que não foi usada anteriormente por este programa; quem sabe o que estava lá. Mas depois dessa chamada para notrandom (), há uma chamada para printf () (que o compilador GCC na verdade otimiza para uma chamada para putchar (), mas não importa) e que sobrescreve a pilha. Então, nos próximos e subseqüentes, quando notrandom () é chamado, a pilha conterá dados obsoletos da execução de putchar () e como putchar () é sempre chamado com os mesmos argumentos, esses dados obsoletos sempre serão os mesmos, também.

Portanto, não há absolutamente nada random sobre esse comportamento, nem os números obtidos dessa maneira têm as propriedades desejáveis ​​de um gerador de números pseudo-randoms bem escrito. De fato, na maioria dos cenários da vida real, seus valores serão repetitivos e altamente correlacionados.

De fato, como outros, eu também consideraria seriamente demitir alguém que tentasse passar essa idéia como um "RNG de alto desempenho".

Comportamento indefinido significa que os autores dos compiladores estão livres para ignorar o problema porque os programadores nunca terão o direito de reclamar o que acontecer.

Embora, em teoria, ao entrar na terra do UB, tudo pode acontecer (incluindo um daemon voando pelo nariz ), o que normalmente significa é que os autores do compilador não se importarão e, para variables ​​locais, o valor será o que estiver na memory da pilha naquele ponto .

Isso também significa que muitas vezes o conteúdo será “estranho”, mas fixo ou ligeiramente random ou variável, mas com um padrão evidente claro (por exemplo, aumentando os valores em cada iteração).

Com certeza você não pode esperar que seja um gerador random decente.

O comportamento indefinido é indefinido. Isso não significa que você obtenha um valor indefinido, isso significa que o programa pode fazer qualquer coisa e ainda atender a especificação da linguagem.

Um bom compilador de otimização deve levar

 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); } } 

e compilá-lo para um noop. Isto é certamente mais rápido que qualquer alternativa. Tem a desvantagem de não fazer nada, mas essa é a desvantagem do comportamento indefinido.

Ainda não mencionado, mas os caminhos de código que invocam comportamento indefinido têm permissão para fazer o que o compilador deseja, por exemplo

 void updateEffect(){} 

O que é certamente mais rápido que o seu loop correto, e por causa do UB, está perfeitamente em conformidade.

Por razões de segurança, a nova memory atribuída a um programa deve ser limpa, caso contrário, as informações poderiam ser usadas e as senhas poderiam vazar de um aplicativo para outro. Somente quando você reutiliza memory, você obtém valores diferentes de 0. E é muito provável que, em uma pilha, o valor anterior seja apenas fixo, porque o uso anterior dessa memory é fixo.

Seu exemplo de código específico provavelmente não faria o que você está esperando. Embora, tecnicamente, cada iteração do loop recria as variables ​​locais para os valores r, g e b, na prática, é exatamente o mesmo espaço de memory na pilha. Portanto, ele não será re-randomizado a cada iteração e você acabará atribuindo os mesmos 3 valores para cada uma das 1000 colors, independentemente de quão randoms r, g e b sejam individualmente e inicialmente.

De fato, se funcionasse, eu ficaria muito curioso para saber o que é a re-randomização. A única coisa em que consigo pensar seria uma interrupção intercalada que se encheckboxria no topo daquela pilha, altamente improvável. Talvez a otimização interna que as mantivesse como variables ​​de registro, e não como verdadeiras posições de memory, onde os registradores são reutilizados mais adiante no ciclo, também funcionasse, especialmente se a function de visibilidade definida fosse particularmente faminta por registros. Ainda assim, longe de ser random.

Como a maioria das pessoas aqui mencionou o comportamento indefinido. Indefinido também significa que você pode obter algum valor inteiro válido (felizmente) e, neste caso, isso será mais rápido (como a chamada de function rand não é feita). Mas não use praticamente isso. Tenho certeza de que isso será terrível, pois a sorte não está com você o tempo todo.

Muito ruim! Mau hábito, resultado ruim. Considerar:

 A_Function_that_use_a_lot_the_Stack(); updateEffect(); 

Se a function A_Function_that_use_a_lot_the_Stack() fizer sempre a mesma boot, ela deixará a pilha com os mesmos dados nela. Esses dados são o que chamamos de updateEffect() : sempre o mesmo valor! .

Eu fiz um teste muito simples e não foi nada random.

 #include  int main() { int a; printf("%d\n", a); return 0; } 

Toda vez que eu corri o programa, ele imprimiu o mesmo número ( 32767 no meu caso) – você não pode ficar muito menos random do que isso. Isso é presumivelmente qualquer que seja o código de boot na biblioteca de tempo de execução deixado na pilha. Como ele usa o mesmo código de boot toda vez que o programa é executado, e nada mais varia no programa entre as execuções, os resultados são perfeitamente consistentes.

Você precisa ter uma definição do que você quer dizer com “random”. Uma definição sensata envolve que os valores que você obtém devem ter pouca correlação. Isso é algo que você pode medir. Também não é trivial conseguir de maneira controlada e reproduzível. Assim, o comportamento indefinido não é certamente o que você está procurando.

Existem certas situações em que a memory não inicializada pode ser lida com segurança usando o tipo “unsigned char *” [por exemplo, um buffer retornado do malloc ]. O código pode ler essa memory sem ter que se preocupar com o fato de o compilador lançar a causalidade pela janela, e há momentos em que pode ser mais eficiente ter código preparado para qualquer memory que possa conter do que garantir que os dados não inicializados não sejam lidos ( um exemplo comum disso seria usar o memcpy no buffer parcialmente inicializado, em vez de copiar discretamente todos os elementos que contêm dados significativos).

Mesmo em tais casos, no entanto, deve-se sempre supor que, se qualquer combinação de bytes for particularmente vexatória, sua leitura sempre produzirá esse padrão de bytes (e se um determinado padrão for vexatório na produção, mas não no desenvolvimento, tal padrão não aparecerá até que o código esteja em produção).

A leitura de memory não inicializada pode ser útil como parte de uma estratégia de geração aleatória em um sistema embarcado em que se pode ter certeza de que a memory nunca foi gravada com conteúdo substancialmente não random desde a última vez que o sistema foi ligado e se O processo usado para a memory faz com que seu estado de ativação varie de maneira semi-aleatória. O código deve funcionar mesmo se todos os dispositivos sempre produzirem os mesmos dados, mas em casos onde, por exemplo, um grupo de nós precisa selecionar IDs únicos arbitrários o mais rápido possível, tendo um gerador “não muito random” que dá a metade dos nós a mesma inicial O ID pode ser melhor do que não ter nenhuma fonte inicial de aleatoriedade.

Como outros já disseram, será rápido, mas não random.

O que a maioria dos compiladores fará para variables ​​locais é pegar algum espaço para eles na pilha, mas não se incomodar em configurá-los (a norma diz que eles não precisam, então por que desacelerar o código que você está gerando?).

Neste caso, o valor que você obterá dependerá do que estava anteriormente na pilha – se você chamar uma function antes desta que tem uma centena de variables ​​char locais, todas configuradas como ‘Q’ e depois chamar você, você está funcionando após que retorna, então você provavelmente encontrará seus valores “randoms” se comportando como se você tivesse memset() todos eles em ‘Q’s’.

Importantly for your example function trying to use this, these values wont change each time you read them, they’ll be the same every time. So you’ll get a 100 stars all set to the same colour and visibility.

Also, nothing says that the compiler shouldn’t initialize these value – so a future compiler might do so.

In general: bad idea, don’t do it. (like a lot of “clever” code level optimizations really…)

As others have already mentioned, this is undefined behavior ( UB ), but it may “work”.

Except from problems already mentioned by others, I see one other problem (disadvantage) – it will not work in any language other than C and C++. I know that this question is about C++, but if you can write code which will be good C++ and Java code and it’s not a problem then why not? Maybe some day someone will have to port it to other language and searching for bugs caused by “magic tricks” UB like this definitely will be a nightmare (especially for an inexperienced C/C++ developer).

Here there is question about another similar UB. Just imagine yourself trying to find bug like this without knowing about this UB. If you want to read more about such strange things in C/C++, read answers for question from link and see this GREAT slideshow. It will help you understand what’s under the hood and how it’s working; it’s not not just another slideshow full of “magic”. I’m quite sure that even most of experienced C/c++ programmers can learn a lot from this.

Not a good idea to rely our any logic on language undefined behaviour. In addition to whatever mentioned/discussed in this post, I would like to mention that with modern C++ approach/style such program may not be compile.

This was mentioned in my previous post which contains the advantage of auto feature and useful link for the same.

https://stackoverflow.com/a/26170069/2724703

So, if we change the above code and replace the actual types with auto , the program would not even compile.

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

I like your way of thinking. Really outside the box. However the tradeoff is really not worth it. Memory-runtime tradeoff is a thing, including undefined behavior for runtime is not .

It must give you a very unsettling feeling to know you are using such “random” as your business logic. I woudn’t do it.

Use 7757 every place you are tempted to use uninitialized variables. I picked it randomly from a list of prime numbers:

  1. it is defined behavior

  2. it is guaranteed to not always be 0

  3. it is prime

  4. it is likely to be as statistically random as uninitualized variables

  5. it is likely to be faster than uninitialized variables since its value is known at compile time

There is one more possibility to consider.

Modern compilers (ahem g++) are so intelligent that they go through your code to see what instructions affect state, and what don’t, and if an instruction is guaranteed to NOT affect the state, g++ will simply remove that instruction.

So here’s what will happen. g++ will definitely see that you are reading, performing arithmetic on, saving, what is essentially a garbage value, which produces more garbage. Since there is no guarantee that the new garbage is any more useful than the old one, it will simply do away with your loop. BLOOP!

This method is useful, but here’s what I would do. Combine UB (Undefined Behaviour) with rand() speed.

Of course, reduce rand() s executed, but mix them in so compiler doesn’t do anything you don’t want it to.

And I won’t fire you.

Using uninitialized data for randomness is not necessarily a bad thing if done properly. In fact, OpenSSL does exactly this to seed its PRNG.

Apparently this usage wasn’t well documented however, because someone noticed Valgrind complaining about using uninitialized data and “fixed” it, causing a bug in the PRNG .

So you can do it, but you need to know what you’re doing and make sure that anyone reading your code understands this.