Как стать автором
Обновить

Комментарии 23

Таким же образом устанавливать -1 в тип bool некорректно и он формально не будет ни true, ни false. Хотя в большинстве случаев он будет вести себя как true. В большинстве случаев…

Ммм… А подробнее можно? С числами то всё понятно. А вот bool до «недавнего» времени вообще не существовал и инициализировать то что использовалось вместо него можно было вообще чем угодно.
Например, если неверно задать количество заполняемых байт
#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));
Да каким угодно.
$ 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 к int, ничуть не меньше, чем на том, кто его заполнил странным значением.
С чего вдруг? Integral conversion вполне однозначно говорят: bool преобразованный к intу даёт гарантированно либо 0, либо 1.

И кроме того, следуя логики статьи, не является ли ваш пример основанием для того, чтобы отказаться от использования bool?
Нет. Тут нужно просто запомнить, что bool кажется типом похожим на int или char, но на самом деле нужно относиться к нему уважительно — так, как вы относитесь к объектам. Если объект с виртуальной функций обнулить memsetом — тоже ведь беда будет.
А вот bool до «недавнего» времени вообще не существовал и инициализировать то что использовалось вместо него можно было вообще чем угодно.
Вообще-то _Bool появился в C99 15 лет назад (а в C++ присутствовал с самого начала).
У нас довольно старый проект и в коде можно найти 5 разных булов, которые ещё и могут быть не равны друг другу.
В мире С++ есть еще одна альтернатива memset, а именно типобезопасные алгоритмы std::fill() и std::fill_n(). Интересно было бы еще и их рассмотреть.
есть множество граблей, на которые наступают даже опытные разработчики.

Не путайте местами аргументы.

Я вот не пойму, это стеб или серьезно?
Следующей по опасности, видимо, будет операция деления: Если путать местами делимое и делитель — такая ерунда получается. Но хуже того, даже если ничего не напутать, частное помноженное на делитель не всегда равно делимому!

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'ами — могут быть незамечены и при работе со стеком, и с динамической памятью, перепутанный же порядок при работе со стеком обычно замечают (там остаётся какой-нибудь мусор, который себя быстро проявляет), а при работе с динамической памятью — нет (вначале вся память заполнена нулями, так что пока программа немножко не поработает неправильный вызов не приводит к проблемам).
Вы в серьёз рассуждаете о том, что переменную на стеке можно заполнить нулями «во время компиляции»? 8-O Или это «такая шутка белых, в которую черные не врубаются»?
заполнить нулями «во время компиляции»?
Как видите, если массив заполнен только нулями и используется только для чтения, то выходит как бы да.
такая шутка белых, в которую черные не врубаются
Проверить и подёргать всё же любопытно.
в серьёз
Рыдаю.
Вывод один: не используйте memset в программах на C++.
Мне кажется можно сделать еще более глубокий вывод: не используйте функции С при программирование на С++. Это РАЗНЫЕ языки, с разными подходами.
Ах, если бы… На деле в этом невероятно сложно объяснить. Оба языка требуют большой дисциплины и глубоких знаний, но работать приходится с разными людьми. Например, недавно на одном из совещаний выяснилось, что коллега не знает, что в Си есть указатели на функции. При этом он перешел к нам из Амазона, писал там много на С++. Не могу же я выйти и сказать команде «вы знаете, его бы я вообще не подпускал к коду». Да и не стал бы я так делать. А в результате получаются статьи о том, какой злой memset.
Он не злой, он просто на стороне тьмы. Лично мне достаточно печенюшек, чтобы оставаться на стороне тьмы :)
В 99% проектов, с которыми я работал, никто не знал про оператор sizeof <expression>, и все писали sizeof(type). А правильно будет: type* ptr = malloc(sizeof *ptr). Если привыкнуть опускать скобки, то компилятор не будет давать описаться и написать sizeof type. К сожалению, защиты от malloc(sizeof ptr), когда нужно было написать malloc(sizeof *ptr), я не знаю.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации