Является ли неинициализированная локальная переменная самым быстрым генератором случайных чисел?

317

Я знаю, что неинициализированная локальная переменная - это поведение undefined (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);
    }
}

а также быстрее, чем другой генератор случайных чисел?

  • 86
    +1 Это совершенно законный вопрос. Это правда, что на практике неинициализированные значения могут быть случайными. Тот факт, что они не особенно и что это UB, не заставляет спрашивать так плохо.
  • 34
    @imallett: Абсолютно. Это хороший вопрос, и по крайней мере одна старая игра Z80 (Amstrad / ZX Spectrum) в прошлом использовала свою программу в качестве данных для настройки ландшафта. Так что есть даже прецеденты. Не могу сделать это в эти дни. Современные операционные системы убирают все веселье.
Показать ещё 25 комментариев
Теги:
undefined-behavior
garbage

22 ответа

300

Как отмечали другие, это Undefined Поведение (UB).

На практике это (возможно) фактически (kindof) работает. Чтение из неинициализированного регистра на архитектуре x86 [-64] действительно приведет к результатам мусора и, вероятно, не сделает ничего плохого (в отличие от, например, Itanium, где регистры могут быть помечены как недопустимые, так что чтение распространяется на ошибки, такие как NaN).

Есть две основные проблемы:

  • Это не будет особенно случайным. В этом случае вы читаете из стека, так что вы получите все, что было ранее. Который может быть эффективно случайным, полностью структурированным, пароль, который вы ввели десять минут назад, или рецепт вашей бабушки cookie.

  • Это плохо (капитал "B" ), чтобы позволить подобным образом проникнуть в ваш код. Технически компилятор может вставлять reformat_hdd(); каждый раз, когда вы читаете переменную Undefined. Это не так, но вы все равно не должны этого делать. Не делайте небезопасные вещи. Чем меньше исключений вы делаете, тем более безопасны вы от случайных ошибок все время.

    Более насущная проблема с UB заключается в том, что она полностью управляет вашей программой undefined. Современные компиляторы могут использовать это, чтобы преодолеть огромные полосы вашего кода или даже вернуться во времени. Игра с UB похожа на викторианского инженера, который демонтирует живой ядерный реактор. Там что-то не так, и вы, вероятно, не будете знать половину основополагающих принципов или внедренных технологий. Это может быть хорошо, но вы все равно не должны позволять этому случиться. Посмотрите на другие приятные ответы для деталей.

Кроме того, я бы уволил вас.

  • 5
    В любой архитектуре нет такого понятия, как «неинициализированный регистр». Машина не понимает инициализацию или ее отсутствие; это языковая конструкция.
  • 39
    @Potatoswatter: Регистры Itanium могут содержать NaT (не вещь), который по сути является «неинициализированным регистром». На Itanium чтение из регистра, если вы не написали в него, может прервать вашу программу (подробнее об этом здесь: blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx ). Таким образом, есть веская причина, по которой чтение неинициализированных значений является неопределенным поведением. Это также, вероятно, одна из причин, почему Itanium не очень популярен :)
Показать ещё 55 комментариев
193

Позвольте мне сказать это четко: мы не вызываем поведение undefined в наших программах. Это никогда не бывает хорошей идеей, периодом. Редкие исключения из этого правила; например, если вы являетесь разработчиком библиотеки, реализующим offsetof. Если ваше дело попадает под такое исключение, вы, вероятно, знаете это уже. В этом случае мы знаем, что использование неинициализированных автоматических переменных - это поведение undefined.

Компиляторы стали очень агрессивными с оптимизацией вокруг поведения undefined, и мы можем найти множество случаев, когда поведение undefined привело к нарушениям безопасности. Самый печально известный случай - это, вероятно, удаление нулевого указателя ядра ядра Linux, о котором я упоминаю в мой ответ на С++ ошибка компиляции?, где оптимизация компилятора вокруг поведения undefined превратила конечный цикл в бесконечный.

Мы можем читать CERT Опасные оптимизации и потеря причинности, в которых, среди прочего, говорится:

Все чаще авторы сценариев используют undefinedповедения на языках программирования C и С++ для улучшения оптимизаций.

Часто эти оптимизации мешают способность разработчиков выполнять причинно-следственный анализ на их исходный код, то есть анализ зависимости результатов ниже по течению по предыдущим результатам.

Следовательно, эти оптимизации устраняются причинности в программном обеспечении и повышают вероятность программного обеспечения ошибок, дефектов и уязвимостей.

В частности, в отношении неопределенных значений, отчет о дефекте видеть его в прямом эфире):

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

или, возможно, получить все нули, как в случае с этим модифицированным случаем:

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

Оба этих случая являются вполне приемлемыми формами поведения undefined.

Обратите внимание, что если мы находимся на Itanium, мы могли бы получить значение trap:

[...], если регистр имеет специальное значение не-вещь, чтение ловушек регистра, за исключением нескольких инструкций [...]

Другие важные примечания

Интересно отметить дисперсию между gcc и clang, отмеченную в проекте UB Canaries, о том, как они хотят использовать undefined по отношению к неинициализированной памяти. В статье отмечается (внимание мое):

Конечно, мы должны полностью понять, что любое такое ожидание не имеет ничего общего с языковым стандартом и все, что связано с тем, что делает конкретный компилятор, либо потому, что поставщики этого компилятора не хотят использовать этот UB или просто потому, что они еще не использовали его. Если нет реальной гарантии от поставщика компилятора, , мы хотели бы сказать, что пока неиспользованные UB - это бомбы замедленного действия: theyre ждет, чтобы уйти в следующем месяце или в следующем году, когда компилятор становится немного более агрессивным.

Как указывает Matthieu M. Что должен знать каждый программист C undefined Поведение № 2/3, также относится к этому вопросу. В нем говорится, среди прочего (акцент мой):

Важная и страшная вещь, которую нужно осознать, состоит в том, что практически любой оптимизация на основе поведения undefined может запускаться buggy code в любое время в будущем. Встраивание, циклическая развертка, память продвижение и другие оптимизации будут улучшаться, и значительная часть их причины для существования заключается в том, чтобы оптимизации, такие как выше.

Для меня это глубоко неудовлетворительно, частично потому, что компилятор неизбежно оказывается обвиненным, но также потому, что это означает, что огромные тела C-кода - это наземные мины, ожидающие взорваться.

Для полноты я должен, вероятно, упомянуть, что реализации могут выбрать корректное поведение undefined, например gcc позволяет вводить пул через объединения а в С++ это похоже на поведение undefined. Если это так, реализация должна документировать его, и это обычно не будет переносимым.

  • 1
    + (int) (PI / 3) для примеров вывода компилятора; реальный пример того, что UB - это, ну, UB .
  • 2
    Использование UB фактически было торговой маркой отличного хакера. Эта традиция существует уже, вероятно, 50 лет и более. К сожалению, компьютеры теперь обязаны минимизировать влияние UB из-за плохих людей. Мне очень понравилось узнавать, как делать классные вещи с помощью машинного кода UB или чтения / записи портов и т. Д. В 90-х годах, когда ОС не была настолько способна защитить пользователя от себя.
Показать ещё 17 комментариев
152

Нет, это ужасно.

Поведение использования неинициализированной переменной undefined как в C, так и в С++, и очень маловероятно, чтобы такая схема имела желательные статистические свойства.

Если вам нужен "быстрый и грязный" генератор случайных чисел, то rand() - ваш лучший выбор. В его реализации все, что он делает, это умножение, добавление и модуль.

Самый быстрый генератор, о котором я знаю, требует использования uint32_t в качестве типа псевдослучайной переменной I и использования

I = 1664525 * I + 1013904223

для генерации последовательных значений. Вы можете выбрать любое начальное значение I (называемое семенем), которое берет ваше воображение. Очевидно, вы можете закодировать этот встроенный. В качестве модуля действует стандартно гарантированное обертывание неподписанного типа. (Численные константы отбираются у этого замечательного научного программиста Дональда Кнута.)

  • 9
    Представленный вами «линейный конгруэнтный» генератор хорош для простых приложений, но только для некриптографических приложений. Можно предсказать его поведение. См., Например, « Расшифровка линейного конгруэнтного шифрования » самого Дона Кнута (IEEE Transactions по теории информации, том 31).
  • 24
    @ Джей по сравнению с унитарной переменной для быстрого и грязного? Это гораздо лучшее решение.
Показать ещё 4 комментария
38

Хороший вопрос!

Undefined не означает, что он случайный. Подумайте об этом, значения, которые вы получили в глобальных неинициализированных переменных, оставались там системой или вашими/другими приложениями. В зависимости от того, что ваша система делает с более не используемой памятью и/или какие значения генерирует система и приложения, вы можете получить:

  • Всегда то же самое.
  • Будь одним из небольшого набора значений.
  • Получить значения в одном или нескольких небольших диапазонах.
  • См. множество значений, делящихся на 2/4/8 из указателей в 16/32/64-битной системе.
  • ...

Значения, которые вы получите полностью, зависят от того, какие неслучайные значения оставлены системой и/или приложениями. Таким образом, действительно будет некоторый шум (если ваша система не уничтожит больше не используемую память), но пул значений, из которого вы рисуете, ни в коем случае не будет случайным.

Для локальных переменных ситуация становится намного хуже, потому что они поступают непосредственно из стека вашей собственной программы. Существует очень хороший шанс, что ваша программа будет фактически записывать эти позиции стека во время выполнения другого кода. Я оцениваю шансы на удачу в этой ситуации очень низко, и "случайное" изменение кода вы делаете попытку этой удачи.

Читайте о randomness. Как вы увидите, случайность - это очень специфическое и труднодоступное свойство. Это распространенная ошибка думать, что если вы просто возьмете то, что трудно отслеживать (например, ваше предложение), вы получите случайное значение.

  • 7
    ... и это исключает все оптимизации компилятора, которые полностью уничтожили бы этот код.
  • 0
    6 ... Вы получите различную «случайность» в Debug и Release. Неопределенный означает, что вы делаете это неправильно.
Показать ещё 2 комментария
30

Много хороших ответов, но позвольте мне добавить еще один и подчеркнуть, что на детерминированном компьютере ничто не является случайным. Это справедливо как для чисел, создаваемых псевдо-RNG, так и для кажущихся "случайных" чисел, найденных в областях памяти, зарезервированных для локальных переменных 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;
}

Когда я компилирую этот код с GCC на машине Linux и запускаю его, он оказывается довольно неприятным детерминированным:

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".

  • 1
    «В детерминированном компьютере нет ничего случайного» - на самом деле это не так. Современные компьютеры содержат всевозможные датчики, которые позволяют генерировать истинную , непредсказуемую случайность без отдельных аппаратных генераторов. В современной архитектуре значения /dev/random часто отбираются из таких аппаратных источников и на самом деле являются «квантовым шумом», то есть действительно непредсказуемыми в лучшем физическом смысле этого слова.
  • 2
    Но ведь это не детерминированный компьютер, не так ли? Теперь вы полагаетесь на вклад окружающей среды. В любом случае, это выводит нас далеко за рамки обсуждения обычного псевдо-ГСЧ и «случайных» битов в неинициализированной памяти. Также ... посмотрите на описание / dev / random, чтобы оценить, насколько далеко зашли разработчики, чтобы убедиться, что случайные числа криптографически безопасны ... именно потому, что входные источники не являются чистым, некоррелированным квантовым шумом, но скорее, потенциально сильно коррелированные показания датчика с небольшой степенью случайности. Это тоже довольно медленно.
28

Undefined поведение означает, что авторы компиляторов могут игнорировать проблему, потому что у программистов никогда не будет права жаловаться на происходящее.

Хотя в теории при входе в землю UB может случиться что-то (включая демон, летящий с вашего носа), что обычно означает, что авторам-компиляторам просто все равно, и для локальных переменных значение будет тем, что находится в памяти стека в этой точке.

Это также означает, что часто контент будет "странным", но фиксированным или слегка случайным или переменным, но с явным очевидным шаблоном (например, увеличение значений на каждой итерации).

Конечно, вы не можете ожидать, что это приличный случайный генератор.

26

Undefined поведение undefined. Это не означает, что вы получаете значение undefined, это значит, что программа может что-то делать и все еще соответствует спецификации языка.

Хороший оптимизирующий компилятор должен принимать

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. Это, безусловно, быстрее, чем любая альтернатива. Недостатком является то, что он ничего не сделает, но это недостаток поведения undefined.

  • 3
    Многое зависит от того, является ли цель компилятора помочь программистам создавать исполняемые файлы, отвечающие требованиям домена, или же цель состоит в том, чтобы создать наиболее «эффективный» исполняемый файл, поведение которого будет соответствовать минимальным требованиям стандарта C, без Рассмотрим, будет ли такое поведение служить какой-либо полезной цели. Что касается первой цели, то использование кода, использующего некоторые произвольные начальные значения для r, g, b, или запуск ловушки отладчика, если это целесообразно, было бы более полезным, чем превращение кода в nop. Что касается последней цели ...
  • 2
    ... оптимальный компилятор должен определить, какие входные данные вызовут выполнение вышеуказанного метода, и исключить любой код, который будет иметь значение только при получении таких входных данных.
Показать ещё 3 комментария
17

Не упоминается, но пути к коду, вызывающие поведение undefined, позволяют делать все, что хочет компилятор, например

void updateEffect(){}

Это, безусловно, быстрее, чем ваш правильный цикл, и из-за UB, идеально соответствует.

17

Из-за соображений безопасности новая память, назначенная программе, должна быть очищена, в противном случае информация может быть использована, и пароли могут протекать из одного приложения в другое. Только при повторном использовании памяти вы получаете разные значения, чем 0. И очень вероятно, что в стеке предыдущее значение просто фиксировано, потому что предыдущее использование этой памяти исправлено.

12

Пример вашего конкретного кода, вероятно, не будет делать то, что вы ожидаете. Технически каждая итерация цикла повторно создает локальные переменные для значений r, g и b, на практике это то же самое пространство памяти в стеке. Следовательно, он не будет повторно рандомизирован с каждой итерацией, и вы в конечном итоге назначаете одинаковые 3 значения для каждого из 1000 цветов, независимо от того, насколько случайны r, g и b являются индивидуально и изначально.

В самом деле, если бы это сработало, мне было бы очень интересно узнать, что такое рандомизация. Единственное, о чем я могу думать, это чередование прерываний, которые могут быть поставлены на вершине этого стека, что маловероятно. Возможно, внутренняя оптимизация, которая сохранила их как регистровые переменные, а не как истинные ячейки памяти, где регистры снова используются в нижнем цикле, также будет хитростью, особенно если функция видимости в определенном режиме является голодной. Тем не менее, далеко не случайно.

11

Я выполнил очень простой тест, и он не был случайным вообще.

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}

Каждый раз, когда я запускал программу, она печатала то же число (32767 в моем случае) - вы не можете получить гораздо менее случайное, чем это. Это, по-видимому, независимо от кода запуска в библиотеке времени выполнения, оставшейся в стеке. Так как он использует один и тот же код запуска каждый раз, когда запускается программа, и ничто другое не меняется в программе между прогонами, результаты совершенно согласованы.

  • 0
    Хорошая точка зрения. Результат сильно зависит от того, где этот генератор случайных чисел вызывается в коде. Это скорее непредсказуемо, чем случайно.
11

Действительно плохо! Плохая привычка, плохой результат. Рассмотрим:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

Если функция A_Function_that_use_a_lot_the_Stack() делает всегда ту же инициализацию, она оставляет стек с теми же данными на нем. Эти данные являются тем, что мы вызываем updateEffect(): всегда одинаковое значение!.

11

Как большинство людей упоминает поведение undefined. undefined также означает, что вы можете получить некоторое действительное целочисленное значение (к счастью), и в этом случае это будет быстрее (поскольку вызов функции rand не выполняется). Но практически не используйте его. Я уверен, что это будет ужасным результатом, потому что удача не с вами все время.

  • 1
    Очень хороший момент! Это может быть прагматический трюк, но действительно тот, который требует удачи.
  • 1
    В этом нет абсолютно никакой удачи. Если компилятор не оптимизирует неопределенное поведение, значения, которые вы получите, будут совершенно детерминированными (= полностью зависят от вашей программы, ее входных данных, ее компилятора, используемых библиотек, времени его потоков, если у него есть потоки). Проблема в том, что вы не можете рассуждать об этих значениях, поскольку они зависят от деталей реализации.
Показать ещё 1 комментарий
10

Вам нужно определить, что вы подразумеваете под "случайным". Разумное определение предполагает, что полученные вами значения должны иметь небольшую корреляцию. Это то, что вы можете измерить. Это также не является тривиальным для достижения в контролируемой, воспроизводимой манере. Таким образом, поведение undefined, конечно, не то, что вы ищете.

5

Существуют определенные ситуации, в которых неинициализированная память может быть безопасно прочитана с использованием типа "unsigned char *" [например. буфер возвращается из malloc]. Код может читать такую ​​память, не беспокоясь о том, что компилятор бросает причинность из окна, и бывают случаи, когда может быть более эффективным создание кода для чего-либо, что может содержать память, чем для обеспечения того, чтобы неинициализированные данные не были прочитаны ( обычным примером этого будет использование memcpy в частично инициализированном буфере, а не дискретное копирование всех элементов, содержащих содержательные данные).

Тем не менее, даже в таких случаях следует всегда предполагать, что если какая-либо комбинация байтов будет особенно досадной, чтение ее всегда будет приводить к тому, что паттерн байтов (и если какой-то образец будет досадным в производстве, но не в разработке, такой шаблон не будет отображаться до тех пор, пока код не будет создан).

Чтение неинициализированной памяти может быть полезно как часть стратегии случайного генерации во встроенной системе, где можно быть уверенным, что память никогда не была написана с существенно-неслучайным содержимым с момента последнего включения системы и если производственный процесс, используемый для памяти, приводит к тому, что состояние включения питания изменяется в полуслучайном порядке. Код должен работать, даже если все устройства всегда дают одни и те же данные, но в тех случаях, когда, например, группе узлов нужно как можно быстрее выбирать произвольные уникальные идентификаторы, имея "не очень случайный" генератор, который дает половину узлов один и тот же начальный идентификатор может быть лучше, чем не иметь никакого исходного источника случайности вообще.

  • 2
    «если какая-либо комбинация байтов будет особенно неприятной, ее чтение всегда будет приводить к такой схеме байтов» - пока вы не закодируете код, чтобы справиться с этим шаблоном, после чего он больше не будет раздражающим, и в будущем будет прочитан другой шаблон.
  • 0
    @SteveJessop: Точно. Моя линия о разработке против производства была призвана передать аналогичное понятие. Код не должен заботиться о том, что находится в неинициализированной памяти, кроме смутного понятия «некоторая случайность может быть хорошей». Если на поведение программы влияет содержимое одного фрагмента неинициализированной памяти, это, в свою очередь, может повлиять на содержимое фрагментов, которые будут получены в будущем.
5

Как говорили другие, он будет быстрым, но не случайным.

То, что большинство компиляторов сделает для локальных переменных, - это захватить некоторое пространство для них в стеке, но не утруждать себя настройкой на что-либо (стандарт говорит, что им это не нужно, поэтому зачем замедлять код, который вы генерируете?).

В этом случае значение, которое вы получите, будет зависеть от того, что было ранее в стеке - если вы вызываете функцию до этого, у которой есть сотня локальных переменных char, все установлены на "Q", а затем вызывают вы будете функционировать после того, как это вернется, тогда вы, вероятно, найдете, что ваши "случайные" значения ведут себя так, как если бы вы memset() их всех в "Q".

Важно, что для вашего примера, использующего эту функцию, эти значения не меняются каждый раз, когда вы их читаете, они будут одинаковыми каждый раз. Таким образом, вы получите 100 звезд, все настроены на тот же цвет и видимость.

Кроме того, ничто не говорит о том, что компилятор не должен инициализировать это значение, поэтому будущий компилятор может это сделать.

В общем: плохая идея, не делай этого. (как много "умных" оптимизаций уровня кода действительно...)

  • 2
    Вы делаете серьезные прогнозы относительно того, что произойдет, хотя ничего из этого не гарантировано благодаря UB. Это также не так на практике.
3

Мне нравится ваш образ мыслей. Действительно вне коробки. Однако компромисс действительно не стоит этого. Компромисс между памятью и временем выполнения, в том числе поведение undefined для среды выполнения не.

Это должно дать вам очень тревожное ощущение, что вы используете такие "случайные", как ваша бизнес-логика. Я этого не сделаю.

3

Не хорошая идея полагаться на нашу логику поведения языка undefined. В дополнение к тому, что упоминалось/обсуждалось в этом посте, я хотел бы упомянуть, что с современным подходом/стилем С++ такая программа не может компилироваться.

Это было упомянуто в моем предыдущем посте, в котором содержится преимущество функции Авто и полезной ссылки для нее.

https://stackoverflow.com/questions/6434971/how-much-is-too-much-with-c11-auto-keyword

Итак, если мы изменим приведенный выше код и заменим фактические типы авто, программа даже не будет компилироваться.

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

Используйте 7757 каждое место, в которое вы соблазняетесь использовать неинициализированные переменные. Я выбрал его случайным образом из списка простых чисел:

  • определяется поведение

  • гарантировано не всегда 0

  • это просто

  • он, скорее всего, будет статистически случайным, как uninitualized переменные

  • он, вероятно, будет быстрее, чем неинициализированные переменные, поскольку его значение известно во время компиляции

3

Как уже упоминалось, это поведение undefined (UB), но оно может "работать".

За исключением проблем, уже упомянутых другими, я вижу еще одну проблему (недостаток) - она ​​не будет работать ни на одном другом языке, кроме C и С++. Я знаю, что этот вопрос касается С++, но если вы можете написать код, который будет хорошим С++ и Java-кодом, и это не проблема, то почему бы и нет? Возможно, когда-нибудь кому-то придется переносить его на другой язык и искать ошибки, вызванные "волшебными трюками" UB, как это определенно будет кошмаром (особенно для неопытного разработчика C/С++).

Здесь возникает вопрос о другом подобном UB. Представьте себе, что вы пытаетесь найти такую ​​ошибку, не зная об этом UB. Если вы хотите больше узнать о таких странных вещах в C/С++, прочитайте ответы на вопрос из ссылки и посмотрите this GREAT слайд-шоу. Это поможет вам понять, что под капотом и как оно работает; это не просто еще одно слайд-шоу, полное "магии". Я вполне уверен, что даже большинство опытных программистов на C/С++ могут многое узнать из этого.

1

Есть еще одна возможность рассмотреть.

Современные компиляторы (ahem g++) настолько умны, что они просматривают ваш код, чтобы увидеть, какие инструкции влияют на состояние, а что нет, и если инструкция гарантированно НЕ влияет на состояние, g++ просто удалит эту инструкцию.

Итак, вот что будет. g++ обязательно увидит, что вы читаете, выполняете арифметику, сохраняете, что по сути является значением для мусора, которое производит больше мусора. Поскольку нет никакой гарантии, что новый мусор более полезен, чем старый, он просто избавится от вашей петли. Bloop!

Этот метод полезен, но вот что я буду делать. Объедините UB (Undefined Поведение) с частотой rand().

Конечно, уменьшите rand(), но смешайте их, чтобы компилятор не делал ничего, что вам не нужно.

И я не убью тебя.

  • 0
    Мне очень трудно поверить, что компилятор может решить, что ваш код делает что-то глупое, и удалить это. Я ожидаю, что это только оптимизирует неиспользуемый код , а не нежелательный код. У вас есть воспроизводимый контрольный пример? В любом случае, рекомендация UB опасна. Кроме того, GCC не единственный компетентный компилятор, поэтому несправедливо выделять его как «современный».
0

Использование неинициализированных данных для случайности не обязательно является плохим, если все сделано правильно. Фактически, OpenSSL делает именно это, чтобы засеять его PRNG.

По-видимому, это использование не было хорошо документировано, потому что кто-то заметил, что Valgrind жалуется на использование неинициализированных данных и "фиксирует" его, вызывая ошибку в PRNG.

Итак, вы можете это сделать, но вам нужно знать, что вы делаете, и убедиться, что кто-то, читающий ваш код, понимает это.

  • 1
    Это будет зависеть от вашего компилятора, который ожидается с неопределенным поведением, как мы можем видеть из моего ответа, Clang сегодня не будет делать то, что они хотят.
  • 5
    То, что OpenSSL использовал этот метод как вход энтропии, не говорит о том, что это было хорошо. В конце концов, единственным другим источником энтропии, который они использовали, был PID . Не совсем хорошее случайное значение. От человека, который полагается на такой плохой источник энтропии, я не буду ожидать хорошего суждения об их другом источнике энтропии. Я просто надеюсь, что люди, которые в настоящее время поддерживают OpenSSL, ярче.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню