Eu sei que a variável local não inicializada é um comportamento indefinido ( UB ), e também o valor pode ter representações de interceptação que podem afetar a operação adicional, mas às vezes eu quero usar o número aleatório apenas para representação visual e não usá-lo em outra parte do programa, por exemplo, defina algo com cores aleatórias 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 aleatórios?
c++
c
undefined-behavior
garbage
ggrr
fonte
fonte
Respostas:
Como outros observaram, esse é um comportamento indefinido (UB).
Na prática, (provavelmente) realmente (meio que) funcionará. A leitura de um registro não inicializado nas arquiteturas x86 [-64] realmente produzirá resultados de lixo e provavelmente não fará nada de ruim (em oposição a, por exemplo, Itanium, onde os registros podem ser sinalizados como inválidos , de modo que lê erros de propagação como NaN).
Existem dois problemas principais:
Não será particularmente aleatório. Nesse caso, você está lendo da pilha, para obter o que estava lá anteriormente. Que pode ser efetivamente aleatória, completamente estruturada, a senha digitada há dez minutos ou a receita de biscoitos da sua avó.
É uma má prática (maiúscula 'B') permitir que coisas como essa entrem no seu código. Tecnicamente, o compilador pode inserir
reformat_hdd();
toda vez que você lê uma variável indefinida. Não vai , mas você não deve fazê-lo de qualquer maneira. Não faça coisas inseguras. Os menos exceções que você faz, mais seguro você é com os erros acidentais todo o tempo.O problema mais urgente do UB é que ele torna o comportamento de todo o programa indefinido. Os compiladores modernos podem usar isso para eliminar grandes partes do seu código ou até mesmo voltar no tempo . Brincar com o UB é como um engenheiro vitoriano desmantelando um reator nuclear ativo. Há um zilhão de coisas a dar errado, e você provavelmente não conhecerá metade dos princípios subjacentes ou da tecnologia implementada. Ele pode estar bem, mas você ainda não deve deixar que isso aconteça. Veja as outras respostas legais para obter detalhes.
Além disso, eu demiti-lo.
fonte
unreachable()
e excluir metade do seu programa. Isso acontece na prática também. Esse comportamento neutralizou completamente o RNG em alguma distribuição Linux, acredito; A maioria das respostas nesta pergunta parece assumir que um valor não inicializado se comporta como um valor. Isso é falso.Deixe-me dizer isso claramente: não invocamos comportamentos indefinidos em nossos programas . Nunca é uma boa ideia, ponto final. Existem raras exceções a esta regra; por exemplo, se você é um implementador de biblioteca implementando offsetof . Se o seu caso se enquadra nessa exceção, você provavelmente já sabe disso. Nesse caso, sabemos que o uso de variáveis automáticas não inicializadas é um comportamento indefinido .
Os compiladores se tornaram muito agressivos com otimizações em relação ao 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 de verificação de ponteiro nulo do kernel do Linux, mencionada na minha resposta ao bug de compilação do C ++? onde uma otimização do compilador em torno de um comportamento indefinido transformou um loop finito em um infinito.
Podemos ler as Otimizações Perigosas e a Perda de Causalidade do CERT ( vídeo ) que dizem, entre outras coisas:
Especificamente em relação a valores indeterminados, o relatório de defeitos padrão C 451: Instabilidade de variáveis automáticas não inicializadas contribui para algumas leituras interessantes. 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 valores indeterminados diferentes em pontos diferentes do programa.
Não conheço nenhum exemplo em que isso ocorra, mas neste momento não podemos descartar isso.
Exemplos reais, não o resultado que você espera
É improvável que você obtenha valores aleatórios. Um compilador poderia otimizar completamente o loop ausente. Por exemplo, com este caso simplificado:
O clang o otimiza ( veja ao vivo ):
ou talvez obtenha todos os zeros, como neste caso modificado:
veja ao vivo :
Ambos os casos são formas perfeitamente aceitáveis de comportamento indefinido.
Observe que, se estivermos em um Itanium, podemos acabar com um valor de interceptação :
Outras notas importantes
É interessante notar a variação entre gcc e clang observada no projeto UB Canaries sobre o quanto eles estão dispostos a aproveitar o comportamento indefinido em relação à memória não inicializada. O artigo observa ( ênfase minha ):
Como Matthieu M. salienta, o que todo programador C deve saber sobre o comportamento indefinido nº 2/3 também é relevante para esta questão. Diz entre outras coisas ( ênfase minha ):
Por uma questão de completude, eu provavelmente deveria mencionar que as implementações podem optar por tornar o comportamento indefinido bem definido; por exemplo, o gcc permite punções de tipo através de uniões enquanto em C ++ isso parece um comportamento indefinido . Se for esse o caso, a implementação deve documentá-lo e isso geralmente não será portátil.
fonte
Não, é terrível.
O comportamento de usar uma variável não inicializada é indefinido em C e C ++, e é muito improvável que esse esquema tenha propriedades estatísticas desejáveis.
Se você deseja um gerador de números aleatórios "rápido e sujo", então
rand()
é a sua melhor aposta. Na sua implementação, tudo o que faz é uma multiplicação, uma adição e um módulo.O gerador mais rápido que conheço exige que você use a
uint32_t
como o tipo da variável pseudo-aleatóriaI
e useI = 1664525 * I + 1013904223
para gerar valores sucessivos. Você pode escolher qualquer valor inicial
I
(chamado de semente ) que seja do seu agrado. Obviamente, você pode codificar essa linha. A envolvente garantida por padrão de um tipo não assinado atua como módulo. (As constantes numéricas são escolhidas a dedo pelo notável programador científico Donald Knuth.)fonte
rand()
não é adequado ao objetivo e deve ser totalmente reprovado, na minha opinião. Hoje em dia, você pode fazer o download de geradores de números aleatórios amplamente licenciados e amplamente superiores (por exemplo, Mersenne Twister), que são quase tão rápidos quanto com a maior facilidade, portanto não há necessidade de continuar usando o altamente defeituosorand()
Boa pergunta!
Indefinido não significa que é aleatório. Pense nisso: os valores que você obteria em variáveis não inicializadas globais foram deixados lá pelo sistema ou por seus / outros aplicativos em execução. Dependendo do que o seu sistema faz com a memória não usada e / ou que tipo de valores o sistema e os aplicativos geram, você pode obter:
Os valores que você obterá dependem completamente de quais valores não aleatórios são deixados pelo sistema e / ou aplicativos. Portanto, de fato haverá algum ruído (a menos que seu sistema limpe não mais a memória usada), mas o conjunto de valores do qual você extrairá nunca será aleatório.
As coisas pioram muito para as variáveis locais, porque elas vêm diretamente da pilha do seu próprio programa. Há uma chance muito boa de que seu programa grave 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 alteração de código 'aleatória' que você faz tenta essa sorte.
Leia sobre aleatoriedade . Como você verá, a aleatoriedade é uma propriedade 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), terá um valor aleatório.
fonte
Muitas boas respostas, mas permitam-me acrescentar outra e enfatizar que, em um computador determinístico, nada é aleatório. Isso ocorre tanto para os números produzidos por um pseudo-RNG quanto para os números aparentemente "aleatórios" encontrados em áreas da memória reservadas para variáveis locais C / C ++ na pilha.
MAS ... há uma diferença crucial.
Os números gerados por um bom gerador de pseudoaleatórios têm propriedades que os tornam estatisticamente semelhantes aos empates verdadeiramente aleatórios. Por exemplo, a distribuição é uniforme. A duração do ciclo é longa: você pode obter milhões de números aleatórios antes que o ciclo se repita. A sequência não está correlacionada automaticamente: por exemplo, você não começará a ver padrões estranhos surgirem se você pegar cada segundo, terceiro ou 27º número ou se observar dígitos específicos nos números gerados.
Por outro lado, os números "aleatórios" deixados na pilha não possuem nenhuma dessas propriedades. Seus valores e sua aparente aleatoriedade dependem inteiramente de como o programa é construído, como é compilado e como é otimizado pelo compilador. A título de exemplo, aqui está uma variação da sua ideia como um programa independente:
Quando eu compilo esse código com o GCC em uma máquina Linux e o executo, ele se mostra bastante desagradável:
Se você analisasse o código compilado com um desmontador, poderia reconstruir o que estava acontecendo, em detalhes. A primeira chamada para notrandom () usou uma área da pilha que não era usada por este programa anteriormente; quem sabe o que estava lá. Mas depois dessa chamada para notrandom (), há uma chamada para printf () (que o compilador GCC realmente otimiza para uma chamada para putchar (), mas não importa) e que substitui a pilha. Portanto, nos próximos momentos seguintes e subsequentes, quando notrandom () for 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 aleatório nesse comportamento, nem os números obtidos dessa maneira têm nenhuma das propriedades desejáveis de um gerador de números pseudoaleatórios 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 tentou passar essa idéia como um "RNG de alto desempenho".
fonte
/dev/random
muitas vezes são semeados de tais fontes de hardware e são de fato "ruído quântico", ou seja, verdadeiramente imprevisível no melhor sentido físico da palavra.Comportamento indefinido significa que os autores dos compiladores são livres para ignorar o problema, porque os programadores nunca terão o direito de reclamar, aconteça o que acontecer.
Enquanto na teoria, ao entrar em terra UB, tudo pode acontecer (incluindo um daemon voando pelo nariz ), o que normalmente significa é que os autores do compilador simplesmente não se importam e, para variáveis locais, o valor será o que estiver na memória da pilha naquele momento .
Isso também significa que frequentemente o conteúdo será "estranho", mas fixo ou levemente aleatório ou variável, mas com um padrão evidente claro (por exemplo, valores crescentes a cada iteração).
Com certeza você não pode esperar que seja um gerador aleatório decente.
fonte
O comportamento indefinido é indefinido. Isso não significa que você obtenha um valor indefinido, significa que o programa pode fazer qualquer coisa e ainda atender à especificação de idioma.
Um bom compilador de otimização deve levar
e compilá-lo para um noop. Isso é certamente mais rápido do que qualquer alternativa. Ele tem o lado negativo de não fazer nada, mas esse é o lado negativo do comportamento indefinido.
fonte
-Wall -Wextra
.Ainda não mencionado, mas os caminhos de código que invocam comportamentos indefinidos podem fazer o que o compilador desejar, por exemplo
O que é certamente mais rápido que o loop correto e, por causa do UB, é perfeitamente compatível.
fonte
Por motivos de segurança, é necessário limpar a nova memória atribuída a um programa, caso contrário, as informações podem ser usadas e as senhas podem vazar de um aplicativo para outro. Somente quando você reutiliza a memória, 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 memória é fixo.
fonte
Seu exemplo de código específico provavelmente não faria o que você esperava. Enquanto tecnicamente cada iteração do loop recria as variáveis locais para os valores r, g e b, na prática, é exatamente o mesmo espaço de memória na pilha. Portanto, ele não será randomizado novamente a cada iteração, e você acabará atribuindo os mesmos 3 valores para cada uma das 1000 cores, independentemente de quão aleatórios r, g e b sejam individual e inicialmente.
De fato, se funcionasse, eu ficaria muito curioso sobre o que o está re-randomizando. A única coisa em que consigo pensar é em uma interrupção intercalada que é empilhada no topo dessa pilha, altamente improvável. Talvez a otimização interna que as manteve como variáveis de registro e não como locais de memória verdadeiros, onde os registros são reutilizados mais abaixo no loop, também ajudaria, especialmente se a função de visibilidade definida estiver com muita fome de registro. Ainda assim, longe de ser aleatório.
fonte
Como a maioria das pessoas aqui mencionou comportamento indefinido. Indefinido também significa que você pode obter algum valor inteiro válido (felizmente) e, nesse caso, isso será mais rápido (pois a chamada de função aleatória não é feita). Mas não use praticamente. Estou certo de que isso terá resultados terríveis, pois a sorte não está com você o tempo todo.
fonte
Muito ruim! Mau hábito, mau resultado. Considerar:
Se a função
A_Function_that_use_a_lot_the_Stack()
sempre faz a mesma inicialização, ela deixa a pilha com os mesmos dados. Esses dados são o que chamamosupdateEffect()
: sempre o mesmo valor! .fonte
Eu realizei um teste muito simples, e não foi aleatório.
Toda vez que rodava o programa, ele imprimia o mesmo número (
32767
no meu caso) - você não pode ser muito menos aleatório do que isso. Isso é presumivelmente qualquer que seja o código de inicialização na biblioteca de tempo de execução deixado na pilha. Como ele usa o mesmo código de inicialização toda vez que o programa é executado, e nada mais varia no programa entre as execuções, os resultados são perfeitamente consistentes.fonte
Você precisa ter uma definição do que você quer dizer com 'aleatório'. Uma definição sensata envolve que os valores obtidos devem ter pouca correlação. Isso é algo que você pode medir. Também não é trivial alcançar de maneira controlada e reproduzível. Portanto, um comportamento indefinido certamente não é o que você está procurando.
fonte
Há certas situações em que a memória não inicializada pode ser lida com segurança usando o tipo "char não assinado *" [por exemplo, um buffer retornado de
malloc
]. O código pode ler essa memória sem ter que se preocupar com o compilador jogando a causalidade pela janela, e há momentos em que pode ser mais eficiente preparar o código para qualquer coisa que a memória possa conter do que garantir que dados não inicializados não sejam lidos ( um exemplo comum disso seria usarmemcpy
um buffer parcialmente inicializado, em vez de copiar discretamente todos os elementos que contêm dados significativos).Mesmo nesses casos, no entanto, deve-se sempre supor que, se alguma combinação de bytes for particularmente vexatória, a leitura sempre produzirá esse padrão de bytes (e se um determinado padrão seria 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 da memória não inicializada pode ser útil como parte de uma estratégia de geração aleatória em um sistema incorporado, em que é possível ter certeza de que a memória nunca foi gravada com conteúdo substancialmente não aleatório desde a última vez em que o sistema foi ligado e se a fabricação O processo usado para a memória faz com que seu estado de inicialização varie de maneira semi-aleatória. O código deve funcionar mesmo que todos os dispositivos sempre produzam os mesmos dados, mas nos casos em que, por exemplo, um grupo de nós precisa selecionar IDs arbitrários e exclusivos o mais rápido possível, tendo um gerador "não muito aleatório" que fornece a metade dos nós a mesma inicial O ID pode ser melhor do que não ter nenhuma fonte inicial de aleatoriedade.
fonte
Como outros já disseram, será rápido, mas não aleatório.
O que a maioria dos compiladores fará por variáveis locais é pegar algum espaço para elas na pilha, mas não se preocupe em defini-las para nada (o padrão diz que elas não precisam, por que diminuir o código que você está gerando?).
Nesse caso, o valor que você obterá dependerá do que estava anteriormente na pilha - se você chamar uma função antes desta com centenas de variáveis de caracteres locais definidas como 'Q' e depois chamar sua função após como retorna, provavelmente você verá que seus valores "aleatórios" se comportam como se
memset()
todos eles tivessem um 'Q'.Importante para a sua função de exemplo que tenta usar isso, esses valores não serão alterados toda vez que você os ler, eles serão os mesmos sempre. Assim, você terá 100 estrelas definidas com a mesma cor e visibilidade.
Além disso, nada indica que o compilador não deve inicializar esses valores - portanto, um compilador futuro poderá fazê-lo.
Em geral: má ideia, não faça. (como muitas otimizações de nível de código "inteligentes" realmente ...)
fonte
Como outros já mencionaram, esse é um comportamento indefinido ( UB ), mas pode "funcionar".
Exceto pelos problemas já mencionados por outros, vejo outro problema (desvantagem) - ele não funcionará em nenhum outro idioma que não seja C e C ++. Eu sei que esta pergunta é sobre C ++, mas se você pode escrever um código que será bom código C ++ e Java e não é um problema, por que não? Talvez um dia alguém tenha que portá-lo para outro idioma e procurar bugs causados por
"truques de mágica"como este definitivamente será um pesadelo (especialmente para um desenvolvedor C / C ++ inexperiente).Aqui há dúvidas sobre outro UB semelhante. Imagine-se tentando encontrar um bug como esse sem saber sobre esse UB. Se você quiser ler mais sobre coisas tão estranhas em C / C ++, leia as respostas para a pergunta no link e veja esta GRANDE apresentação de slides. Isso o ajudará a entender o que está oculto e como está funcionando; não é apenas mais uma apresentação de slides cheia de "magia". Tenho certeza de que mesmo a maioria dos programadores experientes em C / c ++ pode aprender muito com isso.
fonte
Não é uma boa idéia confiar nossa lógica no comportamento indefinido da linguagem. Além do que foi mencionado / discutido neste post, gostaria de mencionar que, com a abordagem / estilo C ++ modernos, esse programa pode não ser compilado.
Isso foi mencionado no meu post anterior, que contém a vantagem do recurso automático e do link útil para o mesmo.
https://stackoverflow.com/a/26170069/2724703
Portanto, se alterarmos o código acima e substituirmos os tipos reais por auto , o programa nem será compilado.
fonte
Eu gosto do seu modo de pensar. Realmente fora da caixa. No entanto, a troca realmente não vale a pena. A troca de memória e tempo de execução é uma coisa, inclusive o comportamento indefinido para o tempo de execução não é .
É necessário que você tenha uma sensação muito perturbadora ao saber que está usando algo "aleatório" como a lógica de negócios. Eu não faria isso.
fonte
Use
7757
todos os lugares em que você for tentado a usar variáveis não inicializadas. Eu escolhi aleatoriamente a partir de uma lista de números primos:é um comportamento definido
é garantido que nem sempre será 0
é primo
é provável que seja tão estatisticamente aleatório quanto variáveis não ritualizadas
é provável que seja mais rápido que as variáveis não inicializadas, pois seu valor é conhecido no momento da compilação
fonte
Há mais uma possibilidade a considerar.
Compiladores modernos (ahem g ++) são tão inteligentes que passam pelo seu código para ver quais instruções afetam o estado e o que não afetam, e se uma instrução garantir que NÃO afeta o estado, o g ++ simplesmente removerá essa instrução.
Então aqui está o que vai acontecer. O g ++ definitivamente verá que você está lendo, executando aritmética, salvando, o que é essencialmente um valor de lixo, que produz mais lixo. Como não há garantia de que o novo lixo seja mais útil que o antigo, ele simplesmente eliminará seu loop. BLOOP!
Este método é útil, mas aqui está o que eu faria. Combine UB (comportamento indefinido) com velocidade rand ().
Obviamente, reduza
rand()
s executados, mas misture-os para que o compilador não faça nada que você não queira.E eu não vou te despedir.
fonte
Usar dados não inicializados para aleatoriedade não é necessariamente uma coisa ruim se feito corretamente. De fato, o OpenSSL faz exatamente isso para propagar seu PRNG.
Aparentemente, esse uso não foi bem documentado, no entanto, porque alguém notou Valgrind reclamando sobre o uso de dados não inicializados e "corrigindo" os dados, causando um bug no PRNG .
Portanto, você pode fazê-lo, mas precisa saber o que está fazendo e garantir que qualquer pessoa que esteja lendo seu código entenda isso.
fonte