memset — сторона тьмы
После прочтения статьи Самая опасная функция в мире С/С++ я счёл полезным углубиться во зло, таящееся в тёмном погребе memset, и написать дополнение, чтобы шире раскрыть суть проблемы.
В языке Си повсеместно используется memset(), таящий в себе множество ловушек. Выдержка из C++ Reference:
void * memset ( void * ptr, int value, size_t num );
Fill block of memory
Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char).
Parameters
ptr — Pointer to the block of memory to fill.
value — Value to be set. The value is passed as an int, but the function fills the block of memory using the unsigned char conversion of this value.
num — Number of bytes to be set to the value. size_t is an unsigned integral type.
Return Value
ptr is returned.
Как уже неоднократно подмечено, есть множество граблей, на которые наступают даже опытные разработчики. Из описанного в статье Andrey2008 краткое обобщение типичных ошибок:
№1. Пытаясь вычислить размер массива, либо структуры, не используйте sizeof() для указателей на массив/структуру, он вернёт вам размер указателя 4 или 8 байт, вместо размера массива/структуры.
№2. Третий аргумент memset() принимает на вход количество байт, а не количество элементов, не учитывая тип данных. Добавлю ещё, например, тип int может занимать как 4, так и 8 байт, в зависимости от архитектуры. На этот случай следует использовать sizeof(int).
№3. Не путайте местами аргументы. Правильная последовательность это указатель, значение, длина в байтах.
№4. Не используйте memset при работе с объектами класса.
Но это лишь вершина айсберга.
Альтернатива memset
memset — это низкоуровневая функция, обязывающая разработчика принимать во внимание все особенности архитектуры компьютера и её использование должно быть обосновано. Давайте для начала рассмотрим альтернативу = , вместо memset, говорят это позволяет инициализировать массив или строку на этапе компиляции, что должно повышать быстродействие программы, в отличии от memset (также ZeroMemory), инициализирующих данные во время исполнения. Я решил это проверить.
If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.
Заодно такая инициализация снимает проблемы №1, №2, №3 с путаницей параметров и размеров буфера. То есть второй и третий аргумент местами мы не перепутаем, размер передавать не надо. Давайте же посмотрим как такой код преобразуют компиляторы. Все компиляторы сразу проверить я не могу, под рукой оказались gcc входящий в android-ndk-r10c, а также gcc в убунту 14.04.
Давайте посмотрим как ведёт себя компилятор на таком куске кода:
Итак, без оптимизации (-O0) инициализация массива компилируется в такой ассемблерный код (просматриваем бинарники с помощью objdump):
Как и ожидалось, без оптимизации мы получаем run-time код, который будет кушать O(n) процессорного времени (где n длина буфера). Что же сделает компилятор с оптимизацией (-O3) можем видеть ниже.
gcc -O3, 32-bit, ARM
Видим, что кусок кода с обнулением в run-time просто пропал, мы получили обещанную производительность O(1), давайте разберёмся откуда же свои значения берёт printf? Нас интересует вот этот кусочек:
То есть компилятор просто выкинул массив, а вместо его значений использует 0, как заложенную на этапе компиляции константу. Хорошо, но что же происходит если мы будем использовать memset? Давайте посмотрим несколько кусочков objdump-а, например, под ARM:
Без оптимизации -O0:
Без оптимизации -O0:
С оптимизацией -O3:
То есть оптимизация просто убирает вызов memset, вставляя его inline. При таких раскладах memset будет всегда работать за O(n) времени, а вот инициализация с помощью = при оптимизации работает за константу, в нашем случае и вовсе не отнимая тактов процессора, нагло выбрасывая сам факт существования массива и подменяя все его элементы нулями. Но давайте посмотрим, всегда ли это так и что будет если мы запишем ненулевое значение после инициализации? Тестовая функция примет вот такой вид:
После компиляции получаем уже знакомый блок кода:
И выглядит это так, как будто компилятор нам вставил оптимизированную версию memset. А давайте посмотрим что будет, если размер массива значительно вырастет? Скажем, не 25 байт, а 25 килобайт!
Строка = <0>переходит на сторону тьмы, memset ликует!
Однако, не будем забываться, всё же проблему с параметрами мы решили, перепутать аргументы теперь не удастся.
Инициализация строки
Также не будет лишним рассмотреть вариант инициализации массива = «». В языке Си используются нуль-терминированные строки, то есть первый символ с байтовым значением 0x00 означает конец строки. Поэтому для инициализации строки нет смысла обнулять все элементы, достаточно лишь обнулить первый. Вот некоторые способы инициализировать пустую строку:
Самый надёжный способ, как будет работать инициализация через = «» снова разобрать objdump. Без оптимизации ничего особенного мы не увидим, там всё аналогично = , рассмотрим сразу с опцией -O3. Итак компилируем под ARM:
И, внезапно, получаем обнуление всех элементов массива.
Та ну ладно! Зачем в нуль-терминированной строке обнулять все неиспользованные символы?! Достаточно же обнулить один единственный байт. Хм, а если там будет 25 тысяч байт, что оно сделает? А вот что:
Похоже, тёмный memset преследует нас. Если вы всё ещё хотите сражаться против тьмы, то стоит упомянуть какие ещё ловушки вас поджидают.
memset может инициализировать числа неправильными значениями
Если вы хотите заполнить массив целых чисел ненулевыми значениями, ознакомьтесь с побайтовым заполнением данных.
Рассмотрим как это получается. Вот имеем скажем массив int, передаём вторым параметром единицу, что происходит?
0x01010101 — в шестнадцатеричной записи каждый байт будет заполнен единицей, а правильное значение
0x00000001 будет невозможно задать функцией memset. Но на самом деле это не баг, это фича.
Вот только незнание этих фич приводит к непредсказуемым ошибкам.
memset может установить невалидное значение
Если в элементы double установить байты -1, мы получим значение Not-A-Number (NaN), а в последствии последующих вычислений, каждая операция со значением NaN будет возращать NaN, таким образом нарушая всю цепочку вычислений.
Таким же образом устанавливать -1 в тип bool некорректно и он формально не будет ни true, ни false. Хотя в большинстве случаев он будет вести себя как true. В большинстве случаев…
И последнее, memset предназначен только для работы с простыми структурами данных. Никогда не используйте memset с управляемыми структурами данных, эта функция предназначена только для низкоуровневых операций.
В статье использованы материалы memset is evil.
Также читайте про уязвимости функции printf.
Источник
memset() in C with examples
memset() is used to fill a block of memory with a particular value.
The syntax of memset() function is as follows :
Note that ptr is a void pointer, so that we can pass any type of pointer to this function.
Let us see a simple example in C to demonstrate how memset() function is used:
Explanation: (str + 13) points to first space (0 based index) of the string “GeeksForGeeks is for programming geeks.”, and memset() sets the character ‘.’ starting from first ‘ ‘ of the string up to 8 character positions of the given string and hence we get the output as shown above.
Exercise :
Predict the output of below program.
Note that the above code doesn’t set array values to 10 as memset works character by character and an integer contains more than one bytes (or characters).
However, if we replace 10 with -1, we get -1 values. Because representation of -1 contains all 1s in case of both char and int.
This article is contributed by MAZHAR IMAM KHAN. If you like GeeksforGeeks and would like to contribute, you can also write an article using write.geeksforgeeks.org or mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
Источник
Using memset for integer array in C
Like the above use of memset, can we initialize only a few integer array index values to 1 as given below?
10 Answers 10
No, you cannot use memset() like this. The manpage says (emphasis mine):
The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c .
Since an int is usually 4 bytes, this won’t cut it.
If you (incorrectly!!) try to do this:
then the first 6 int s in the array will actually be set to 0x01010101 = 16843009.
The only time it’s ever really acceptable to write over a «blob» of data with non-byte datatype(s), is memset(thing, 0, sizeof(thing)); to «zero-out» the whole struture/array. This works because NULL, 0x00000000, 0.0, are all completely zeros.
The solution is to use a for loop and set it yourself:
Short answer, NO.
Long answer, memset sets bytes and works for characters because they are single bytes, but integers are not.
On Linux, OSX and other UNIX like operating systems where wchar_t is 32 bits and you can use wmemset() instead of memset() .
Note that wchar_t on MS-Windows is 16 bits so this trick may not work.
The third argument of memset is byte size. So you should set total byte size of arr[15]
However probably, you should want to set value 1 to whole elements in arr. Then you’ve better to set in the loop.
Because memset() set 1 in each bytes. So it’s not your expected.
Since nobody mentioned it.
Although you cannot initialize the integers with value 1 using memset, you can initialize them with value -1 and simply change your logic to work with negative values instead.
For example, to initialize the first 6 numbers of your array with -1 , you would do
Furthermore, if you only need to do this initialization once, you can actually declare the array to start with values 1 from compile time.
No, you can’t [portably] use memset for that purpose, unless the desired target value is 0 . memset treats the target memory region as an array of bytes, not an array of int s.
A fairly popular hack for filling a memory region with a repetitive pattern is actually based on memcpy . It critically relies on the expectation that memcpy copies data in forward direction
This is, of course, a pretty ugly hack, since the behavior of standard memcpy is undefined when the source and destination memory regions overlap. You can write your own version of memcpy though, making sure it copies data in forward direction, and use in the above fashion. But it is not really worth it. Just use a simple cycle to set the elements of your array to the desired value.
Источник
Memset in с linux
НАЗВАНИЕ
memory: memccpy, memchr, memcmp, memcpy, memset — функции для работы с памятью
ОПИСАНИЕ
Данные функции максимально эффективно манипулируют с областями памяти (массивами символов, размер которых определяется счетчиком, а не нулевым байтом в конце). Функции не выполняют проверку переполнения массива-приемника.
Функция memccpy копирует символы из области памяти s2 в s1 до тех пор, пока либо не будет скопировано первое вхождение символа c, либо не будет скопировано n символов. Функция возвращает указатель на следующий после c элемент массива s1 в случае, если c был найден, или пустой указатель (NULL), если c не был найден среди первых n символов s2.
Функция memchr возвращает указатель на первое вхождение символа c в n первых символов области памяти s или пустой указатель, если символ c не встретился.
Функция memcmp сравнивает первые n символов своих аргументов. Функция возвращает целое число, меньшее, равное или большее 0, если s1, соответственно, лексикографически меньше, равно или больше s2.
Функция memcpy копирует n символов из области памяти s2 в s1. Результат функции — s1.
Функция memset заполняет первые n байт области памяти s символами c. Результат функции — s.
Для удобства пользователей все эти функции описаны во включаемом файле .
ОГРАНИЧЕНИЯ
Функция memcmp использует аппаратное сравнение символов. Поэтому результаты сравнения цепочек, содержащих символы со старшим битом, равным единице, машинно-зависимы. В данной системе подобные символы трактуются как отрицательные числа.
Источник
memset () в C с примерами
memset () используется для заполнения блока памяти определенным значением.
Синтаксис функции memset () следующий:
Обратите внимание, что ptr является пустым указателем , поэтому мы можем передать любой тип указателя на эту функцию.
Давайте посмотрим на простой пример в C, чтобы продемонстрировать, как используется функция memset ():
// C программа для демонстрации работы memset ()
#include
#include
char str[50] = «GeeksForGeeks is for programming geeks.» ;
printf ( «\nBefore memset(): %s\n» , str);
// Заполняем 8 символов, начиная с str [13], с ‘.’
memset (str + 13, ‘.’ , 8* sizeof ( char ));
printf ( «After memset(): %s» , str);
Объяснение: (str + 13) указывает на первый пробел (индекс на основе 0) строки «GeeksForGeeks предназначен для программирования вундеркиндов.», А memset () устанавливает символ «.» начиная с первого » строки до 8 позиций символов данной строки, и, следовательно, мы получаем вывод, как показано выше.
// C программа для демонстрации работы memset ()
#include
#include
void printArray( int arr[], int n)
printf ( «%d » , arr[i]);
// Заполнить весь массив 0.
memset (arr, 0, n* sizeof (arr[0]));
printf ( «Array after memset()\n» );
Упражнение:
Прогнозировать вывод программы ниже.
// C программа для демонстрации работы memset ()
#include
#include
void printArray( int arr[], int n)
printf ( «%d » , arr[i]);
// Заполнить весь массив 100.
memset (arr, 10, n* sizeof (arr[0]));
printf ( «Array after memset()\n» );
Обратите внимание, что приведенный выше код не устанавливает значения массива равными 10, поскольку memset работает символ за символом, а целое число содержит более одного байта (или символов).
Однако, если мы заменим 10 на -1, мы получим -1 значения. Потому что представление -1 содержит все 1 в случае как char, так и int.
Эта статья предоставлена МАЖАР ИМАМ ХАН . Если вы как GeeksforGeeks и хотели бы внести свой вклад, вы также можете написать статью с помощью contribute.geeksforgeeks.org или по почте статьи contribute@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.
Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.
Источник