Memset c что это

от admin

memset — сторона тьмы

После прочтения статьи Самая опасная функция в мире С/С++ я счёл полезным углубиться во зло, таящееся в тёмном погребе memset, и написать дополнение, чтобы шире раскрыть суть проблемы.

В языке Си повсеместно используется memset(), таящий в себе множество ловушек. Выдержка из C++ Reference:

Как уже неоднократно подмечено, есть множество граблей, на которые наступают даже опытные разработчики. Из описанного в статье Andrey2008 краткое обобщение типичных ошибок:

№1. Пытаясь вычислить размер массива, либо структуры, не используйте sizeof() для указателей на массив/структуру, он вернёт вам размер указателя 4 или 8 байт, вместо размера массива/структуры.

№2. Третий аргумент memset() принимает на вход количество байт, а не количество элементов, не учитывая тип данных. Добавлю ещё, например, тип int может занимать как 4, так и 8 байт, в зависимости от архитектуры. На этот случай следует использовать sizeof(int).

№3. Не путайте местами аргументы. Правильная последовательность это указатель, значение, длина в байтах.

№4. Не используйте memset при работе с объектами класса.

Но это лишь вершина айсберга.

Альтернатива memset

memset — это низкоуровневая функция, обязывающая разработчика принимать во внимание все особенности архитектуры компьютера и её использование должно быть обосновано. Давайте для начала рассмотрим альтернативу = , вместо memset, говорят это позволяет инициализировать массив или строку на этапе компиляции, что должно повышать быстродействие программы, в отличии от memset (также ZeroMemory), инициализирующих данные во время исполнения. Я решил это проверить.

Заодно такая инициализация снимает проблемы №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:

С оптимизацией -O3:

Без оптимизации -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.

Memset c что это

Функция memset из заголовочного файла string.h позволяет инициализировать блок динамической памяти начальными значениями. Она имеет следующий прототип:

destination : указатель на блок выделенной памяти

value : значение, которым инициализуются значения в блоке выделенной памяти

N : размер блока выделенной памяти

Например, выделим блок памяти для 5 чисел типа int и инициализируем все байты нулями:

Стоит отметить, что инициализируются не числа int, которые здесь составляют массив ptr, а каждый байт блока памяти.

memcpy

Функция memcpy копирует определенное количество байт из одного блока памяти в другой. Она имеет следующий прототип:

destination : указатель на блок памяти, в который копируем

source : указатель на блок памяти, из которого копируем

N : количество копируемых байтов

Например, скопируем 5 байт из одного блока памяти в другой:

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

Также стоит отметить, что блоки памяти для копирования данных не обязательно представляют блок объектов типа char , они могут быть предназначены для любых других типов, например, int :

Другой пример — копирование структур:

Читать:
Как отключить автоматический вход

Поскольку функция memcpy принимают указатели, то в примере выше в данную функцию передаются адреса структур.

Сравнение объектов. memcmp

Функция memcmp сравнивает определенное количество байт двух объектов. Она имеет следующий прототип:

Первый и второй параметры представляют указатели на блоки памяти, которые сравниваются. Последний параметр представляет количество байт блоков p1 и p2, которые сравниваются.

Функция возвращает 0, если N байтов обоих объектов равны.

Если первый несовпадающий байт первого блока памяти больше соответствующего байта из второго блока, то возвращается число больше 0.

Если первый несовпадающий байт первого блока памяти меньше соответствующего байта из второго блока, то возвращается число меньше 0.

Например, сравним два объекта структуры:

В данном случае сравниваем все байты обоих структур. Поскольку их значения равны, то функция memcmp возвратит 0.

Причем поскольку функция сравнивает последовательно соответствующие байты обоих объектов, а в структурах данные располагаются по порядку определения элементов, то может быть так, что окажутся равны объекты разных типов структур:

Здесь два объекта структур person и employee имееют одни и те же элементы и их значения. Соответственно функция memcmp возвратит 0, несмотря на то, что это разные структуры.

Еще один пример — сравненим массивы:

Здесь массивы не равны, соответственно функция возвратит число, которое не равно 0.

Memset c что это

Memset() is a C++ function. It copies a single character for a specified number of times to an object. It is useful for filling a number of bytes with a given value starting from a specific memory location. It is defined in <cstring> header file.

Syntax:

Memset() converts the value ch to unsigned char and copies it into each of the first n characters of the object pointed to by str[]. If the object is not trivially-copyable (e.g., scalar, array, or a C-compatible struct), the behavior is undefined. If n is greater than the size of the object pointed to by str, the behavior is undefined.

Parameters:

  • str[]: Pointer to the object to copy the character.
  • ch: The character to copy. It can be a character, a normal value as well a boolean value.
  • n: Number of bytes to copy.

Return value: The memset() function returns str, the pointer to the destination string.

Time Complexity: O(N) [For traverse from begin to end of the object]
Auxiliary Space Complexity: O(1)

Example:

Note: We can use memset() to set all values as 0 or -1 for integral data types also. It will not work if we use it to set as other values. The reason is simple, memset works byte by byte.

Boolean Example:

NOTE: For boolean the object must be of bool type for C++. Eg. bool arr[n];

Advantages of memset( ) function

1. Increase readability

The main purpose of memset() function is to transform each character of the whole string into a specific int value before passing it as an input. It is a one-line piece of code, making it highly compact and improving readability overall.

2. Reduce lines of code

The same task has been accomplished more quickly than the laborious technique by utilizing this memset() function, which just accepts input, rather than the unneeded usage of loops to assign and convert the value of each character in the string to an int value.

3. It is Faster

The same task has been accomplished quickly as opposed to the time-consuming way by using this memset() function’s input alone, which eliminates the need for needless loops to assign and convert the value of each character in the string to an int value.

4. Useful in getting rid of Misalignment Problem

The C++ memset() function aids the programmer in solving the misalignment issue. There are instances where you discover that the processor is having trouble with data alignment, which results in a programming mistake. The memcpy() and memcmp() methods in C++ are the best options in this situation.

Memset Definition and use

Definition: Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char).

Does this mean it hard codes a value in a memory address?

memset(&serv_addr,0,sizeof(serv_addr) is the example that I’m trying to understand.

Can someone please explain in a VERY simplified way?

6 Answers 6

memset() is a very fast version of a relatively simple operation:

That is, memset(b, c, l) set the l bytes starting at address b to the value c . It just does it much faster than in the above implementation.

memset() is usually used to initialise values. For example consider the following struct:

If you create one of these on the stack like so:

Then the values in that struct are going to be undefined. They might be zero, they might be whatever values happened to be there from when that portion of the stack was last used. So usually you would follow that line with:

Of course it can be used for other scenarios, this is just one of them. Just think of it as a way to simply set a portion of memory to a certain value.

memset is a common way to set a memory region to 0 regardless of the data type. One can say that memset doesn’t care about the data type and just sets all bytes to zero.

IMHO in C++ one should avoid doing memset when possible since it circumvents the type safety that C++ provides, instead one should use constructor or initialization as means of initializing. memset done on a class instance may also destroy something unintentionally:

a memset on an instance of the above would not do a reference counter decrement properly.

AndersK's user avatar

I guess that serv_addr is some local or global variable of some struct type -perhaps struct sockaddr — (or maybe a class ).

&serv_addr is taking the address of that variable. It is a valid address, given as first argument to memset . The second argument to memset is the byte to be used for filling (zero byte). The last argument to memset is the size, in bytes, of that memory zone to fill, which is the size of that serv_addr variable in your example.

So this call to memset clears a global or local variable serv_addr containing some struct .

In practice, the GCC compiler, when it is optimizing, will generate clever code for that, usually unrolling and inlining it (actually, it is often a builtin, so GCC can generate very clever code for it).

Похожие статьи