Комментарии 23
Таким же образом устанавливать -1 в тип bool некорректно и он формально не будет ни true, ни false. Хотя в большинстве случаев он будет вести себя как true. В большинстве случаев…
Ммм… А подробнее можно? С числами то всё понятно. А вот bool до «недавнего» времени вообще не существовал и инициализировать то что использовалось вместо него можно было вообще чем угодно.
Например, если неверно задать количество заполняемых байт
255
254
Checked
#include <stdbool.h>
void wrong_bool_memset(){
bool b;
memset(&b, -1, 2);
printf("%d\n", (int)b);
printf("%d\n", (int)!b);
if(!b)printf("Checked\n");
}
Вывод:255
254
Checked
Интересно, какие подводные камни могут быть, если заполнение было полным, то есть, если memset(&b, -1, sizeof(bool));
Да каким угодно.
В первом случае
Бороться с «прокисшим» (sour)
$ cat test.cc
int foo(bool x) {
if (x)
return 0;
else
return 42;
}
int bar(bool x) {
return (!!x) * 2;
}
$ gcc -O3 -S test.cc -o-
.file "test.cc"
.text
.p2align 4,,15
.globl _Z3foob
.type _Z3foob, @function
_Z3foob:
.LFB0:
.cfi_startproc
cmpb $1, %dil
sbbl %eax, %eax
andl $42, %eax
ret
.cfi_endproc
.LFE0:
.size _Z3foob, .-_Z3foob
.p2align 4,,15
.globl _Z3barb
.type _Z3barb, @function
_Z3barb:
.LFB1:
.cfi_startproc
movzbl %dil, %eax
addl %eax, %eax
ret
.cfi_endproc
.LFE1:
.size _Z3barb, .-_Z3barb
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
.section .note.GNU-stack,"",@progbits
В первом случае
1
— это true
, всё остальное false
, во втором вместо 0
и 2
получаем половину «таблицы менделеева». Вам достаточно?Бороться с «прокисшим» (sour)
bool
ом — эта та ещё задачка. Некоторые аж в ассемблер залазят, чтобы точно всё сработало.По моему мнению, если уж и вылезет баг втором случае, то вина того, кто использовал неявное приведения bool к int, ничуть не меньше, чем на том, кто его заполнил странным значением.
И кроме того, следуя логики статьи, не является ли ваш пример основанием для того, чтобы отказаться от использования bool? (ну, чтоб в ассемблер не лазить лишний раз)
И кроме того, следуя логики статьи, не является ли ваш пример основанием для того, чтобы отказаться от использования bool? (ну, чтоб в ассемблер не лазить лишний раз)
вина того, кто использовал неявное приведения bool к int, ничуть не меньше, чем на том, кто его заполнил странным значением.С чего вдруг? Integral conversion вполне однозначно говорят:
bool
преобразованный к int
у даёт гарантированно либо 0
, либо 1
.И кроме того, следуя логики статьи, не является ли ваш пример основанием для того, чтобы отказаться от использования bool?Нет. Тут нужно просто запомнить, что
bool
кажется типом похожим на int
или char
, но на самом деле нужно относиться к нему уважительно — так, как вы относитесь к объектам. Если объект с виртуальной функций обнулить memset
ом — тоже ведь беда будет.А вот bool до «недавнего» времени вообще не существовал и инициализировать то что использовалось вместо него можно было вообще чем угодно.Вообще-то
_Bool
появился в C99 15 лет назад (а в C++ присутствовал с самого начала).В мире С++ есть еще одна альтернатива memset, а именно типобезопасные алгоритмы std::fill() и std::fill_n(). Интересно было бы еще и их рассмотреть.
Была уже статья на хабре, правда немного другого формата:
Кто быстрее: memset, bzero или std::fill
Кто быстрее: memset, bzero или std::fill
есть множество граблей, на которые наступают даже опытные разработчики.
…
Не путайте местами аргументы.
Я вот не пойму, это стеб или серьезно?
Следующей по опасности, видимо, будет операция деления: Если путать местами делимое и делитель — такая ерунда получается. Но хуже того, даже если ничего не напутать, частное помноженное на делитель не всегда равно делимому!
memset может установить невалидное значение
Если в элементы double установить байты -1, мы получим значение Not-A-Number (NaN)
И с каких пор у нас NaN невалидное значение? Создатели стандарта старались, выделяли специальноые значения для него, придумывали signaling и quiet NaN, как именно они должны себя вести в разных операциях, а тут, бах, невалидно и все тут!
Я бы еще понял еслиб речь шла о денормализованных значениях…
И это еще почти никто (@a_batyr как-то неуверенно это сделал) не упомянул какой кошмар начнется если memset-ом начинать заполнять случайные области на стеке!
Я вот не пойму, это стеб или серьезно?Это абсолютно серьёзно. Количество проектов, в которых перепутаны аргументы memset'а просто-таки не поддаётся перечислению.
Следующей по опасности, видимо, будет операция деления: Если путать местами делимое и делитель — такая ерунда получается.Именно в этом и проблема: если вы перепутаете местами делимое и делитель, то ваша программа нормально работать не будет. А если перепутать аргументы memset'а местами — то будет. До поры, до времени. В 90% случаев последний аргумент
memset
а — нуль, если его передать вторым, то уже неважно что будет в третьем, просто обнуления не произойдёт. Сразу после запуска программы, когда вы получаете от системы «чистенькую» память — всё работает, а если немножко поработать — начинает глючить по странному. И воспроизводимости никакой нет. Очень неприятная ошибка.И это еще почти никто (@a_batyr как-то неуверенно это сделал) не упомянул какой кошмар начнется если memset-ом начинать заполнять случайные области на стеке!Действительно. А дело всё в том, что тут речь идёт не о каких-то «фантазиях на тему», а про наиболее неприятные ошибки, к которым приводит
memset
. Если вы начинаете заполнять «случайные области на стеке», то ваша программа долго не проживёт, вы эту ошибку заметите и исправите. Всё. Неприятно, но не смертельно. А вот ошибки, которые могут сидеть в коде годами и проходить при этом разные тесты — это действительно неприятно. Структура memset
'а такова, что это происходит довольно-таки часто: либо ничего не обнуляется, либо обнуляется слишком мало — заметить это сложно на практике (когда обнуляется слишком много — это как раз обычно сразу замечают).В 90% случаев последний аргумент memsetа — нуль
Ну что-ж… Если их путают даже при обсуждении их путанья, тогда да, верю что проблема существует! :)
Хотя в коде такого ни разу в жизни не видел. Вот разнообразных ошибок с sizeof-ами — сколко угодно…
(пардон промахнулся. это ответ на сообщение khim )
Зависит от специфика проекта. Ошибки с sizeof'ами — могут быть незамечены и при работе со стеком, и с динамической памятью, перепутанный же порядок при работе со стеком обычно замечают (там остаётся какой-нибудь мусор, который себя быстро проявляет), а при работе с динамической памятью — нет (вначале вся память заполнена нулями, так что пока программа немножко не поработает неправильный вызов не приводит к проблемам).
На тему перепутанного 2 и 3 аргумента: www.viva64.com/en/examples/V575
Вы в серьёз рассуждаете о том, что переменную на стеке можно заполнить нулями «во время компиляции»? 8-O Или это «такая шутка белых, в которую черные не врубаются»?
Вывод один: не используйте memset в программах на C++.
Мне кажется можно сделать еще более глубокий вывод: не используйте функции С при программирование на С++. Это РАЗНЫЕ языки, с разными подходами.
Ах, если бы… На деле в этом невероятно сложно объяснить. Оба языка требуют большой дисциплины и глубоких знаний, но работать приходится с разными людьми. Например, недавно на одном из совещаний выяснилось, что коллега не знает, что в Си есть указатели на функции. При этом он перешел к нам из Амазона, писал там много на С++. Не могу же я выйти и сказать команде «вы знаете, его бы я вообще не подпускал к коду». Да и не стал бы я так делать. А в результате получаются статьи о том, какой злой memset.
В 99% проектов, с которыми я работал, никто не знал про оператор sizeof <expression>, и все писали sizeof(type). А правильно будет: type* ptr = malloc(sizeof *ptr). Если привыкнуть опускать скобки, то компилятор не будет давать описаться и написать sizeof type. К сожалению, защиты от malloc(sizeof ptr), когда нужно было написать malloc(sizeof *ptr), я не знаю.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
memset — сторона тьмы