Memchr   memset работа с байтами в массивах


Функция memset

Функция memset() копирует младший байт параметра ch в первые count символов массива, адресуемого параметром buf . Функция возвращает значение указателя buf .

Чаще всего функция memset() используется для инициализации области памяти некоторым известным значением.

Пример

Данный фрагмент инициализирует первые 100 байтов массива, адресуемого указателем buf , нулями. Затем он помещает символы X в первые 10 байтов этого массива и выводит строку XXXXXXXXXX.

Использование memset и memcpy правильно для инициализации массива символов в С++

5 Chani [2013-05-30 20:31:00]

Я хотел инициализировать массив символов с данными из указателя символа. Я написал для этого следующий код:

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

Однако, когда я запускаю свой код, я получаю эти wierd-символы в конце строки:

Не могли бы вы объяснить, почему это происходит? Поскольку я печатаю только массив символов, taht имеет длину всего 16 символов, должно печататься не более 16 символов? Где находятся эти два (иногда нулевые, а иногда и один) символы?

Что еще более важно, я повредил любую память (которая не принадлежит моему символьному массиву c ) моим дополнением?

Самая эффективная замена memcpy

Нужно копировать большую область памяти, не используя string.h. Какой код, заменяющий memcpy, работает предельно эффективно по скорости? Первое, что приходит на ум, — это

Если это не самый быстрый вариант, то как ускорить его на C без ассемблера?

3 ответа 3

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

Можно также почитать разные мелкие манипуляции по оптимизации самого цикла и индексации: http://www.danielvik.com/2010/02/fast-memcpy-in-c.html.

думаю что написать быстрее чем memcpy врят ли удастся — она может быть довольно сильно оптимизированна — библиотека хоть и сишная, но есть практически на любой платформе и реализовать ее могут разработчики платформы. но если уж совсем охота то вот идея — реализация — писть не буду долго. Надо копировать не по байтам отдельным а по 4 или 8 байтов (в зависимости от того 32 битная или 64 битная ли система). просто часто при копировании 1 байта регистр получается задействован не полностью. если совсем писать быструю — то можно написать системно зависимую функу — попробовать поискать функции работы с памятью минуя ядро — в этом вопросе не спец, могу и наврать.

А что можно? memcpy объявлен в memory.h, а не в string.h. Если хотите быстро, возьмите дизассемблерный код от memcpy.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками c или задайте свой вопрос.

Похожие

Подписаться на ленту

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

дизайн сайта / логотип © 2020 Stack Exchange Inc; пользовательское содержимое попадает под действие лицензии cc by-sa 4.0 с указанием ссылки на источник. rev 2020.11.11.35402

Memchr   memset работа с байтами в массивах

Штатные библиотеки языка Си включают в себя большое количество функций, ориентированных на работу с блоками памяти. К ним, в частности, относятся: memcpy, memmove, memcmp, memset и др. В подавляющем большинстве случаев эти функции реализованы на ассемблере и достаточно качественно оптимизированы. Тем не менее, резерв производительности еще есть и путем определенных ухищрений можно сократить время обработки больших блоков памяти чуть ли не в несколько раз.

Большинство реализаций функции memcpy выглядят приблизительно так: «while (count—) *dst++ = *src++». Этот код имеет, по крайней мере, три проблемы: перекрытие транзакций чтения/записи, невысокую степень параллелизма обработки ячеек и возможность пересечения обоих потоков в одном и том же DRAM-банке.

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

Параллелизм загрузки данных легко усилить, если обращаться к ячейкам с шагом, равным размеру пакетного цикла чтения. Для простоты можно остановится на шаге в 32 байта, но в критичных к быстродействию приложениях, оптимизируемых под процессоры старшего поколения (AMD Athlon, Pentium-4), эту величину рекомендуется определять автоматически или задавать опционально.

Пагубное влияние возможного пересечения потоков данных в одном DRAM-банке в данной схеме исчезает само собой, поскольку потоки обрабатываются последовательно, а не параллельно.

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

Улучшенный вариант реализации memcpy может выглядеть, например, следующим образом:


10%. Увы, устраняя одни проблемы, мы неизбежно создаем другие. Предложенный способ оптимизации memcpy имеет, как минимум, два серьезных недостатка. Во-первых, увеличение количества циклов с одного до трех несет значительные накладные расходы, которых никакими ухищрениями невозможно избежать. Во-вторых, цикл, загружающий данные из оперативной памяти в кэш, фактически работает вхолостую, запихивая полученные ячейки в неиспользуемую переменную, в то время как цикл, записывающий данные в память, вынужден повторно обращаться к уже загруженным ячейкам. Т.е. count/BRUST_LEN ячеек копируются как бы дважды. К сожалению, первый цикл не может непосредственно записывать полученные ячейки в память, поскольку это неизбежно вызовет перекрытие шинных транзакций и лишь ухудшит производительность.

Ассемблерная реализация данного алгоритма, конечно, увеличит его быстродействие, но не намного. Гораздо лучший результат дает использование предвыборки (см. статью «Управление кэшированием в процессорах старших поколений»), но это уже тема другого разговора.
[ Ссылки могут видеть только зарегистрированные пользователи. ]

Рисунок 1. Демонстрация эффективности параллельного копирования памяти. Выигрыш особенно ощутим на процессорах Athlon — целых

Функция memmove, входящая в стандартную библиотеку языка Си, выгодно отличается от своей ближайшей родственницы memcpy тем, что умеет копировать перекрывающиеся блоки памяти. За счет чего это достигается? Если адрес приемника расположен «левее» источника (т.е. лежит в младших адресах) алгоритм копирования реализуется аналогично memcpy, поскольку ячейки памяти переносятся «назад» — в свободную неинициализированную область (см. рис.2, сверху). Единственное условие — количество ячеек памяти, переносимых за одну итерацию, не должно превышать разницу адресов приемника и источника. То есть, если приемник расположен всего в двух байтах от источника, переносить память двойными словами уже не получится!
[ Ссылки могут видеть только зарегистрированные пользователи. ]

Рисунок 2. Копирование перекрывающихся блоков памяти. Если источник расположен правее приемника (верхний рисунок), то перенос ячеек памяти происходит без каких-либо проблем. Напротив, если источник расположен левее приемника (нижний рисунок), то перенос ячеек «вперед» приведет к затиранию источника.

Гораздо сложнее справиться с ситуацией, когда приемник расположен правее источника (т.е. лежит в старших адресах). Попытка скопировать память слева направо приведет к краху, т.к. перенос ячеек будет осуществляться в уже «занятые» адреса с неизбежным затиранием их содержимого. Попросту говоря, memcpy в этом случае будет работать как memset (см. рис. 2 снизу), что явно не входит в наши планы. Как же выйти из этой ситуации? Обратившись к исходным текстам функции memmove (в частности у Microsoft Visual C++ они расположены в каталоге \Microsoft Visual Studio\VC98\CRT\SRC\memmove.c), мы обнаружим следующий подход:

Ага, память копируется с зада наперед, т.е. справа налево. В таком случае затирания ячеек гарантированно не происходит, но. за это приходится платить. Ведь подсистема памяти оптимизирована именно под прямое чтение, и попытка погладить ее «против шерсти» ничего хорошего в плане быстродействия не несет. Насколько сильно это снижает производительность? На этот вопрос нет универсального ответа. В зависимости от особенности архитектуры используемого аппаратного обеспечения эта величина может кол*****ся в несколько раз.

В частности, на Intel P-III/Intel 815EP обратное копирование памяти уступает прямому приблизительно в полтора раза. А вот на AMD Athlon/VIA KT133 разница в скорости между прямым и обратным копированием составляет всего

2%, чем со спокойной совестью можно пренебречь. Тем не менее, компьютеры на основе Athlon/KT133 занимают значительно меньшую долю рынка, нежели системы на базе Intel Pentium, поэтому не стоит закладываться на такую конфигурацию.

При интенсивном использовании memmove общее снижение производительности может оказаться весьма значительным и неудивительно, если у разработчика возникнет жгучее желание хоть немного его поднять. Это действительно возможно сделать, достаточно лишь копировать память не байтами и даже не двойными словами, а. блоками с размером равным разнице адресов приемника и источника. Если размер блока составит хотя бы пару килобайт, память будет копироваться в прямом направлении, хотя и задом наперед. Как это можно реализовать на практике? Рассмотрим следующий пример, написанный на чистом Си без применения ассемблера. Для повышения наглядности отсюда исключен вспомогательный код, обеспечивающий, в частности, обработку ошибок и выравнивание стартовых адресов с последующим переносом «хвоста».

В сравнении со штатной memmove, данная функция работает на 20% быстрее (если разница адресов источника и приемника не превышает размер кэш-памяти первого уровня) и на 30% быстрее при перемещении блоков памяти на большое расстояние. Причем, это еще не предел — переписав функцию MyMemMoveX на ассемблер, мы получим еще больший прирост производительности!

Разумеется, если разница адресов источника и приемника невелика (менее килобайта), то «обмануть» систему не получится, и память будет копироваться скорее в обратном направлении, чем в прямом. При этом накладные расходы на организацию цикла поблочного копирования вызовут значительное снижение производительности, проигрывая штатной memmove в два и более раз. Поэтому на область применения MyMemMoveX наложены определенные ограничения.

Хорошо, но ведь бывают же такие случаи, когда перекрывающиеся области необходимо копировать именно «вперед»? Допустим, у нас имеется поток данных, не поддерживающий позиционирование указателя или, скажем, весь блок памяти на момент начала копирования целиком еще недоступен. Вот вполне жизненный пример (из практики автора) — при перемещении фрагментов изображения в графическом редакторе техническое задание требовало выполнять обновление изображения от начала блока к концу. (Дабы пользователь мог приступать к работе с изображением, не дожидаясь, пока блок будет перемещен целиком). Причем использовать ссылочную организацию данных запрещалось.

Первое, что приходит на ум, — перенос памяти через промежуточный буфер. Все идея реализуется тривиальным кодом вида:

Гуд? Да какой там гуд. Во-первых, предложенный алгоритм удваивает количество потребляемой памяти, что в ряде случаев просто неприемлемо; во-вторых, он в два раза увеличивает время копирования и, наконец, в-третьих, он не решает задачи, поставленной в ТЗ. Ведь обновление начала изображения начнется не сразу, а через довольно продолжительное время, в течение которого будет заполняться временный буфер. Так что не стоит этот алгоритм и обсуждать!

Но постойте — зачем нам копировать весь перемещаемый блок в промежуточный буфер целиком? Достаточно сохранить лишь ту его часть, которая затирается выступающим влево «хвостом». Т.е. максимально разумный размер буфера равен dst — src. Рассмотрим упрощенный вариант алгоритма прямого перемещения памяти, использующий два таких буфера. Назовем его четырехтактным потоковым алгоритмом копирования памяти. Почему «четырехтактным» станет ясно ниже.

Итак, такт первый: memcpy(BUF_1, dst, dst — src) — мы сохраняем память приемника, поскольку этот фрагмент будет затерт в следующем такте (см. рис. 3).

Такт второй: memcpy(dst, src, dst — src) — мы копируем (dst — src) байт из источника в приемник, не беспокоясь о затираемой памяти, т.к. она уже сохранена в буфере.

Такт третий: memcpy(BUF_2, dst + (dst — src), dst — src) — сохраняем следующую порцию данных приемника во втором промежуточном буфере.

Такт четвертый: memcpy(dst+ (dst — src), BUF_1, dst — src) — «выливаем» содержимое буфера BUF_1 на положенное место (оно только что было сохранено в BUF_2).

Все! Первый буфер освобождается и можно смело переходить к такту 1 — «рабочий цикл» нашего «движка» завершен.

Как нетрудно убедиться, копирование происходит только в прямом направлении, причем память приемника обновляется от начала к концу маленькими порциями по (dst — src) байт. (При «мышином» перетаскивании областей изображений в графическом редакторе они действительно недалеко уползают за один шаг; кстати, Microsoft Paint (графический редактор из штатной поставки Windows) при перетаскивании изображений перемещает память именно memmove, поэтому жутко тормозит даже на P-III).

Причем, если разница адресов источника и приемника составляет порядка 4. 8 Кб, то несмотря на двойной перегон памяти к буферам и обратно, предложенный алгоритм даже обгоняет memmove на 10%, — во всяком случае на P-III. А на AMD Athlon/VIA KT133 разница достигает аж 1,7 крат (в пользу нашего алгоритма, естественно), впрочем, это отнюдь не показатель крутости алгоритма, просто VIA KT133 так уж устроен.

А теперь давайте подумаем: можно ли уменьшить количество буферов с двух до одного? Разумеется, да! Ведь на момент завершения второго такта регион [src[0]. src[dst-src]] (на рис.3 он закрашен красным цветом) уже свободен и может использоваться для временного хранения данных. Однако тут есть один подводный камень — если адреса «своих» временных буферов мы можем выбирать самостоятельно с учетом архитектуры и организации DRAM, то адрес источника нам дается «извне» со всеми отсюда вытекающими. Разумеется, ничего невозможно нет и при желании обойтись всего одним буфером при не сильно худшей эффективности — вполне возможно, но это значительно усложнит алгоритм и снизит его наглядность. А алгоритм, надобно сказать, и без того не слишком прозрачен.
[ Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 3. «Четырехтактный» алгоритм прямого переноса памяти с использованием двух промежуточных буферов.

Илон Маск рекомендует:  Относительное позиционирование

[ Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 4. Демонстрация эффективности различных алгоритмов переноса памяти.

[ Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 5. Демонстрация эффективности различных алгоритмов переноса памяти (увеличено).

Оптимизация функции memcmp

Несмотря на то, что функция memcmp не относится к числу самых популярных (так, в MSDN memcpy упоминается 500 раз, а memcmp и memmove — всего 150 и 50 раз соответственно) это еще не дает оснований пренебрегать качеством ее реализации. Начнем с анализа штатных библиотек вашего компилятора. В большинстве случаев сравнение блоков памяти осуществляется приблизительно так:

Фи! Тормозное побайтное сравнение безо всяких попыток оптимизации! Правда, в комплект поставки Visual C++ входит и ассемблерная реализация той же самой функции (ищите ее в каталоге \SRC\Intel). Ну-ка, посмотрим, что там (по соображениям экономии места исходный текст не приводится) — ага, если оба указателя кратны четырем, сравнение ведется двойными словами (что намного быстрее) и лишь в противном случае — по байтам. Гуд? А вот и не гуд! Кратность начальных адресов — условие вовсе необязательное для 32-разрядного сравнения. Если три младших бита обоих указателей равны, функция может выровнять их и самостоятельно, просто сместившись на один, два или три байта «вперед».

Впрочем, эти рассуждения все равно беспредметны, поскольку в режиме оптимизации по скорости (ключ «/O2») Microsoft Visual C++ отказывается от использования ряда библиотечных функций и заменяет их intrinsic-ами (см. «pragma intrinsic» в документации по компилятору). Забавно, но разработчики компилятора, по-видимому сочли, что выполнять множество проверок и «тянуть» за собой несколько вариантов реализации функции сравнения будет нерационально(?) и потому они ограничились одним универсальным решением — тривиальным побайтовым сравнением. Неудивительно, что после такой «оптимизации» быстродействие memcmp значительно ухудшилось.

Чтобы запретить компилятору самовольничать, используйте прагму «function» с указанием имени функции — например, так: «#pragma function(memcmp)». В частности, на P-III это ускорит выполнение функции приблизительно на 36%! Правда, на Athlon разница в производительности будет существо меньше — порядка 10%. Кстати, в защиту Microsoft можно сказать, что ее реализация memcmp на 20%-30% быстрее, чем у Borland C++ 5.5. Но и это еще не предел!

Для memcmp (как и для большинства остальных функций, работающих с памятью) актуальна проблема оптимального чередования DRAM-банков. Если оба сравниваемых блока начинаются с различных страниц одного и того же банка, время доступа к памяти существенно замедляется. Поэтому мы должны уметь отслеживать такую ситуацию, при необходимости увеличивая один из указателей на длину DRAM-страницы. Это повысит скорость выполнения функции приблизительно на 40% на P-III и на целых 60%-70% на AMD Athlon. Правда, тут есть одно «но». Память должна обрабатываться не байтами, а двойными словами, в противном случае прирост производительности составит всего лишь 5% для P-III и немногим менее 30% для AMD Athlon.

Хорошо, а если адреса сравниваемых блоков к нам поступают «извне» и скорректировать их невозможно? Существует два пути: смириться с низкой производительностью или. сравнивать не сами блоки памяти, а их контрольную сумму. Конечно, теоретически не исключено, что контрольные суммы различных блоков памяти «волшебным» образом совпадут, но в подавляющем большинстве случаев эта вероятность настолько мала, что ей вполне можно пренебречь. К тому же, считать контрольную сумму всего блока абсолютно необязательно — достаточно ограничиться одной DRAM-страницей (можно, в принципе, и меньшей величиной — главное, чтобы переключения между страницами одного банка происходили не слишком часто). За счет сокращения количества параллельно обрабатываемых потоков данных с двух до одного, хеш-алгоритм работает намного быстрее штатной функции сравнения памяти, обгоняя ее на


55% на P-III и AMD Athlon, соответственно. Правда, при оптимальном чередовании банков памяти, хеш-алгоритм все же проигрывает функции, сравнивающей память двойными словами. Причем, если на P-III хеш-алгоритм отстает от нее всего на 1%, то на AMD Athlon разрыв в производительности достигает целых 10%!

Таким образом, хеш-алгоритм целесообразно использовать только при неоптимальном чередовании DRAM-банков. Впрочем, категоричность этого утверждения смягчает одна оговорка. Если мы сократим длину хешируемого блока до величины пакетного цикла обмена, на P-III мы получим практически 60% выигрыш в производительности, обогнав самый быстрый алгоритм двойных слов более чем на 20%! Ценой же за это станет постоянное переключение DRAM-страниц и, как следствие, потеря возможности противостоять неблагоприятному чередованию банков памяти. Однако такой значительный прирост скорости стоит того! Увы, этот эффект имеет место лишь на Intel и непереносим на AMD/VIA. С другой стороны, Pentium-ам принадлежит более половины компьютерного рынка и оснований для отказа от предложенного трюка, в общем-то, нет. Тем более, что даже на AMD Athlon он (хеш-алгоритм) работает значительно быстрее штатной функции сравнения памяти. Один из возможных вариантов его реализации будет выглядеть так:

[ Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 6. Демонстрация эффективности различных алгоритмов сравнения блоков памяти.

Особое замечание по функциями Win32 API

В win32 API входит множество функций для работы с блоками памяти, среди которых присутствуют и прямые эквиваленты штатных функций языка Си: CopyMemory (эквивалент memcpy), MoveMemory (эквивалент memmove) и FillMemory (эквивалент memset).

Возникает вопрос: чем лучше пользоваться — функциями операционной системы или функциями самого языка? Ответ: компания Microsoft намеренно заблокировала возможность использования функций ядра операционной системы, включив в заголовочные файлы WINBASE.H и WINNT.H следующий код:

Ну и что здесь особенного? А вот что — строка «Import Library» отсутствует! Следовательно, функция MoveMemory целиком реализована во включаемом файле WINBASE.H, о чем Microsoft нас и предупреждает. Но это еще не конец истории. Скорее, только ее начало.

Давайте, воспользовавшись утилитой DUMBDIN, посмотрим на список функций, экспортируемых «ядерной» библиотекой операционной системы — файлом KERNEL32.DLL. Вопреки логике и здравому смыслу мы обнаружим следующее:

Впрочем, использовать RtlMoveMemory вместо memmove — не очень хорошая идея и Microsoft не зря заблокировала ее вызов. Функция RtlMoveMemory совершенно отвратительно оптимизирована. Во-первых, она не выравнивает адреса перемещаемых блоков памяти, а, во-вторых, перекрывающиеся блоки памяти в случае src Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 7. Сравнительная характеристика штатных функций компилятора Microsoft Visual C++ и эквивалентных им функций операционной системы. Кстати, все они в той или иной степени неоптимальны.

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

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

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

Как же действительно обстоят дела на практике? Об этом можно узнать из таблицы, приведенной ниже и описывающие ключевые особенности оптимизации базовых memory-функций популярных компиляторов и операционной системы Windows 2000. (Операционная система приведена лишь в качестве примера, т.к. использование функций семейства RtlxxxMemory, как было показано выше, нецелесообразно — см. «Особое замечание по функциями Win32 API»):
[ Ссылки могут видеть только зарегистрированные пользователи. ]
Таблица 1. Сводная характеристика качества оптимизации штатных Си-функций и функций ОС для работы с памятью.

Что полезного можно почерпнуть из этой таблицы? Первое, что сразу бросается в глаза: крайне небрежная оптимизация штатных функций в компиляторах от Borland и WATCOM. Создается впечатление, что их разработчики вообще не ставили перед собой задачу достичь если не максимальной, то хотя бы приемлемой производительности.

Гораздо качественнее оптимизированы memory-функции штатной библиотеки компилятора Microsoft Visual C++, которые выгодно отличаются тем, что выравнивают адрес приемника на границу 4 байт, что в ряде случаев значительно увеличивает производительность. Тем не менее, Microsoft Visual C++ не использует никаких прогрессивных алгоритмов оптимизации, описанных в настоящей статье, а функции memcmp он не оптимизирует вообще!

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

Оптимизация строковых штатных Си-функций

Типичная Си-строка (см. рис. 8) представляет собой цепочку однобайтовых символов, завершаемую специальным символом конца строки — нулевым байтом (не путать с символом «0»!), поэтому Си-строки также называют ASCIIZ-стоками (‘Z’ — сокращение от «Zero» — нуль на конце). Это крайне неоптимальная структура данных, особенно для современных 32-разрядных процессоров!

Основной недостаток Си-строк заключается в невозможности быстро определить их длину — для этого нам приходится сканировать всю строку целиком в поисках завершающего ее нуля. Причем поиск завершающего символа должен осуществляется побайтовым сравнением каждого символа строки с нулем. А ведь производительность побайтового чтения памяти крайне низка! Конечно, можно попробовать сделать «ход конем»: загружать ячейки памяти двойными словами, а затем «просвечивать» их сквозь четыре битовых маски, только вряд ли это добавит производительности — скорее лишь сильнее ухудшит ее.

По тем же самым причинам невозможно реализовать эффективное копирование и объединение Си-строк. Действительно, как прикажете копировать строку, не зная какой она длины?

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

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

Как это обстоятельство может быть использовано для оптимизации копирования и объединения Pascal-строк? А вот смотрите:

Листинг. Пример реализации функций объединения Си (слева) и Pascal строк (справа).

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

Интенсивная работа с Си-строками способна серьезно подорвать производительность программы и потому лучше совсем отказаться от их использования. Проблема в том, что мы не можем «самовольно» перейти на Pascal-строки, не изменив все сопутствующие им библиотеки языка Си и API-функций операционной системы. Ведь функции наподобие fopen или LoadLibrary рассчитаны исключительно на ASCIIZ-строки и попытка «скормить» им Pascal-строку ни к чему хорошему не приведет — функция, не обнаружив в положенном месте символа-завершителя строки, залезет совершенно в постороннею память!

Выход состоит в создании «гибридных» Pascal + ASCIIZ-строк, явно хранящих длину строки в специально на то отведенном поле, но вместе с тем, имеющих завершающий ноль на конце строки. Именно так и поступили разработчики класса CString библиотеки MFC, распространяемой вместе с компилятором Microsoft Visual C++.
[ Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 8. Устройство Си, Pascal, Delphi и MFC-строк.

Си-строки могут иметь неограниченную длину, но не могут содержать в себе символа нуля, т.к. он трактуется как завершитель строки. Pascal-строки хранят длину строки в специальном однобайтовом поле, что значительно увеличивает эффективность строковых функций, позволяет хранить в строках любые символы, но ограничивает их размер 256 байтами. Delphi-строки представляют собой разновидность Pascal-строк и отличаются от них лишь увеличенной разрядностью поля длины, теперь строки могут достигать 64Кб длины. MFC-строки — это гибрид Си и Pascal строк с 32-битным полем длины, благодаря чему максимальная длина строки теперь равна 4Гб.

Несложный тест поможет нам сравнить эффективность обработки Си- и MFC-строк на операциях сравнения, копирования и вычисления длины. На последней операции, собственно, и наблюдается наибольших разрыв в производительности, достигающих в зависимости от длины «подопытной» строки от одного до нескольких порядков.

Объединение двух MFC-строк (при условии, что обе они одинаковой длины) осуществляется практически вдвое быстрее, чем аналогичных им Си-строк, что совсем неудивительно, т.к. в первом случае мы обращаемся к вдвое меньшему количеству ячеек памяти. Разумеется, если к концу очень длиной строки дописывается всего несколько символов, то выигрыш от использования MFC-строк окажется много большим и приблизительно составит:
strlen(dst) / strlen(src) крат.

А вот сравнение Си- и MFC-строк происходит одинаково эффективно, точнее одинаково неэффективно, поскольку разработчики библиотеки MFC предпочли побайтовое сравнение сравнению двойными словами, что не самым лучшим образом сказалось на производительности. Забавно, но штатная функция strcmp из комплекта поставки Microsoft Visual C++, похоже, единственная функция сравнения строк, обрабатывающая их не байтами, а двойными словами, что в среднем происходит вдвое быстрее. В общем, наиболее предпочтительное сравнение MFC-строк выглядит так:

[ Ссылки могут видеть только зарегистрированные пользователи. ]
Рисунок 9. Сравнение эффективности MFC и Си-функций, работающий со строками. Как видно, MFC строки более производительны.

Сводная характеристика качества оптимизации штатных Си функций и функций ОС для работы со строками.


В таблицах, приведенных ниже, содержится краткая характеристика качества оптимизации штатных Си-функций и функций операционной системы для работы со строками. Обратите внимание: ни операционная система, ни библиотеки распространенных компиляторов не являются полностью оптимальными и резерв для повышения быстродействия еще есть!

Поэтому, целесообразнее пользоваться вашими собственными реализациями строковых функций, вылизанных «по самые помидоры». Если этого не сделаете вы, никто не оптимизирует вашу программу за вас!
[ Ссылки могут видеть только зарегистрированные пользователи. ]
[ Ссылки могут видеть только зарегистрированные пользователи. ]
Таблица 2. Сводная таблица качества оптимизации штатных Си-функций и функций ОС для работы со строками.

Илон Маск рекомендует:  Что такое код gmp_gcdext

[ Ссылки могут видеть только зарегистрированные пользователи. ]

Почему функции памяти, такие как memset, memchr . находятся в string.h, но не в stdlib.h с другими функциями mem?

Интересно, почему такая функция, как:
-memset
-memmov
-memchr
-memcpy

Существовать в string.h заголовочного файла, но не в stdlib.h файла, где есть другие стандартные функции памяти в качестве распределения динамической памяти: malloc, calloc, realloc, free.

Возможно, было бы лучше объединить их в один заголовок? Что вы думаете об этом? Я не понимаю, почему один набор функций памяти отделен от других и существует в заголовке строки (string.h).

Создан 20 мар. 12 2012-03-20 06:13:05 Anonymous

Это похоже на проблему с реализацией библиотеки C, которую вы используете. Другая библиотека C может выбрать перемещение memcpy в stdlib. – AgA 20 мар. 12 2012-03-20 06:22:15

‘malloc’ и семейная сделка с динамическим распределением памяти. ‘memcpy’ и family имеют дело с последовательностями байтов. ‘strcpy’ и family также имеют дело с последовательностями байтов, несколько иначе. – n.m. 20 мар. 12 2012-03-20 06:22:26

@AgA: Если библиотека C соответствует стандарту ISO, то ‘memcpy’ будет находиться в’ string.h’, а не ‘stdlib.h’. – Blastfurnace 20 мар. 12 2012-03-20 06:29:45

Мое предположение, это чисто исторические причины. Вероятно, это заголовочный файл, в который была введена функция, когда они были впервые введены, и чтобы сохранить максимальную обратную совместимость, стандартный комитет решил оставить их там. – Some programmer dude 20 мар. 12 2012-03-20 06:53:37

Стандартная библиотека C не является моделью согласованного дизайна. – Keith Thompson 20 мар. 12 2012-03-20 06:57:27

3 ответа

Поскольку фактически string.h определяется как стандартный заголовок, который объявляет функции, обрабатывающие массив символов, а не только строки. Такие функции, как memcpy и memset , принимают аргументы, которые рассматриваются как указатели на первый элемент объекта типа массива символов.

(С99, 7.21.1p1) Заголовок объявляет один вид и несколько функций, и определяет один макро полезный для манипулирования массивами типа символов и другие объекты, обработанные как массивы типа символов.

Создан 20 мар. 12 2012-03-20 06:41:18 ouah

, но такие методы, как memset, memchr, memmov, memcpy, работают с типом void * и действительно больше работают с памятью, а не с char, правильно? – user1131997 20 мар. 12 2012-03-20 06:49:15

Вы можете передать любой тип указателя объекта, но элементы массива на самом деле интерпретируются так, как будто они имеют тип ‘unsigned char’. (C99, 7.21.1p3) – ouah 20 мар. 12 2012-03-20 06:55:13

Также, касаясь ‘void *’, обратите внимание, что в K & R C (pre-Standard C) не было типа ‘void’, а параметры таких функций, как’ memcpy’ и ‘memset’, char * ‘, а не’ void * ‘. – ouah 20 мар. 12 2012-03-20 07:11:31

Я бы не подумал о функциях string.h как функции «памяти». Вместо этого я бы назвал их «функциями массива», поскольку они работают с данными, содержащимися в последовательности памяти. Напротив, malloc (и другие) фактически предоставляют услуги памяти, такие как распределение, а не манипулирование данными в области памяти.

В частности, функции в string.h не заботятся ни о каком распределении или освобождении памяти или какой-либо форме управления памятью. Даже такая функция, как char * strerror(int) , которая, как представляется, создает целую новую строку, не выполняет никаких распределений, потому что возвращаемое значение на самом деле является статически выделенной строкой. Другие функции могут возвращать указатель на блок памяти, но на самом деле это только один из их параметров (например, memcpy ). Или они возвращают указатель на начало подстроки ( strtok ) или целое число, представляющее сравнение ( memcmp ).

С другой стороны, stdlib.h также не касается памяти. Конструкция stdlib.h представляет собой операции общего назначения, которые, вероятно, потребуют большого количества программ. Функции памяти просто являются примерами таких фундаментальных операций. Однако другие функции, такие как exit и system , также являются хорошими примерами, но не применяются к памяти.

Теперь есть некоторые функции, которые stdlib.h ИМО могли быть размещены в string.h , в частности, различные функции преобразования ( mbstowcs , wcstombs , atoi , strtod и т.д.), и, возможно, даже функции bsearch и qsort . Эти функции соответствуют тем же принципам, что и функции string.h (они работают на массивах, не возвращают вновь выделенные блоки памяти и т. Д.).

Но с практической точки зрения, даже если он сделал много смысла, чтобы объединить mem* функции с функциями malloc , realloc , calloc и free , стандартная библиотека С никогда будет реорганизована, как это. Такое изменение, безусловно, нарушит код. Кроме того, и string.h существуют так долго и являются и такими полезными, и фундаментальными библиотеками, что изменения, вероятно, нарушат большинство (или, по крайней мере, много) кода C.

В чём разница между strncpy, memcpy и memmove?

strncpy копирует 0-терминированные си-строки, т.е. учитывает символ 0 и после него не копирует. Если нужно, добивает нулями до переданного количества символов(num).

memcpy просто копирует указанное количество байт.

memmove копирует указанное количество байт так, будто это происходит через промежуточный буфер, таким образом source и destination могут перекрываться.

Так в документации написаны различия
strncpy — копирует определенное количество символов и если строка «источник» меньше указанного количества, то результат дополняется нулями.
memcpy — тут без проверок и дополнений.
memmove — есть проверки на перекрытие блоков данных, чтобы при копировании не перетереть данные.

Символы «\r\n.» ничего не значат. Нуль-терминированных строках значение имеет только 0.

Memchr   memset работа с байтами в массивах

НАЗВАНИЕ
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 использует аппаратное сравнение символов. Поэтому результаты сравнения цепочек, содержащих символы со старшим битом, равным единице, машинно-зависимы. В данной системе подобные символы трактуются как отрицательные числа.

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

Memchr   memset работа с байтами в массивах

Операции на строках (или массивах символов) — важная часть многих программ. Библиотека C GNU обеспечивает большой набор строковых сервисных функций, включая функции для копирования, связывания, сравнения, и поиска строк. Многие из этих функций могут также функционировать на произвольных областях памяти; например, функция memcpy может использоваться, чтобы копировать содержимое любого вида массива.

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

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

Этот раздел — быстрый обзор строковых понятий для начинающих программистов. Он описывает, как символьные строки представляются на C. Если Вы уже знакомы с этим материалом, Вы можете пропустить этот раздел.

Строка — массив объектов char. Но строковые переменные, обычно объявляется, как указатели типа char *. Такие переменные не включают пространство для текста строки; он должен быть сохранен где-нибудь в переменной типа массив, строковой константе, или динамически размещенной памяти (см. Главу 3 [Распределение памяти]). Это позволяет Вам сохранить адрес выбранного пространства памяти в переменнуюуказатель. В качестве альтернативы Вы можете сохранять пустой указатель в переменной. Пустой указатель никуда не указывает, так что попытка сослаться на строку, на которую он указывает, получит ошибку.

Обычно, пустой символ, «\0», отмечает конец строки. Например, в тестировании, чтобы видеть, что переменная p указывает на пустой символ, отмечающий конец строки, Вы можете написать ! * P или * p == «\0».

Пустой символ — совершенно отличен от пустого указателя, хотя, и представляется целым числом 0.

Строковые литералы появляются в C программе как строки символов между символами кавычек (‘»‘). В ANSI C, строковые литералы могут также быть сформированы строковой конкатенацией: «a» «b» — то же что «ab». Изменение строковых литералов не допускается GNU С компилятором, потому что литералы помещены в памяти только для чтения.

Символьные массивы, которые являются объявленным const, также не могут изменяться. Это — вообще хороший стиль, объявить, что немодифицируемые строковые указатели будут типа const char *, так как это часто позволяет компилятору C обнаружить случайные изменения, также как обеспечение некоторого количества документации относительно того, что ваша программа предполагает делать со строкой.

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

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

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

Функции, которые функционируют на произвольных блоках памяти, имеют имена, начинающиеся «mem» (типа memcpy) и неизменно имеют аргумент, который определяет размер (в байтах) блока рабочей памяти. Аргументы массива и возвращаемые значения для этих функций имеют тип void*, и как стиль, элементы этих массивов упоминаются как «байты». Вы можете передавать любой вид указателя на эти функции, а оператор sizeof полезен при вычислении значения аргумента size.

Напротив, функции, которые функционируют специально на строках, имеют имена, начинающиеся «str» (типа strcpy) и ищут пустой символ, чтобы завершить строку вместо того, чтобы требовать, чтобы был передан явный аргумент размера. (Некоторые из этих функций принимают заданную максимальную длину, но они также проверяют преждевременное окончание с пустым символом.) аргументы массива и возвращаемые значения для этих функций имеют тип char *, и элементы массива упоминаются как «символы».

В многих случаях, имеется, и «mem» и «str» версии функции. Которая является более подходящей, зависит от контекста. Когда ваша программа манипулирует произвольными массивами или блоками памяти, Вы должны всегда использовать «mem» функции. С другой стороны, когда Вы манипулируете строками с нулевым символом в конце, обычно более удобно использовать «str» функции, если Вы не знаете длину строки заранее.

Вы можете получить длину строки, используя функцию strlen. Эта функция объявлена в заголовочном файле «string.h».

strlen («привет, мир») =>12

Когда функция strlen применяется к символьному массиву, она возвращает длину сохраненной строки, а не размер резервирования. Вы можете получить размер резервирования символьного массива, который содержит строку, используя оператор sizeof:

char string[32] = «привет, мир»; sizeof (string) => 32 strlen (string) => 12

Вы можете использовать функции, описанные в этом разделе, чтобы копировать содержимое строк и массивов, или конкатенировать содержимое одной строки c другой. Эти функции объявлены в заголовочном файле «string.h».

Все эти функции возвращают адрес целевого массива.

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

Все функции, которые имеют проблемы при копировании между накладывающимися массивами, явно идентифицированы в этом руководстве. В дополнение к функциям в этом разделе, имеется несколько, других например sprintf (см. раздел 7.9.7 [Форматируемые функции вывода]) и scanf (см. раздел 7.11.8 [Форматируемые функции ввода]).


Значение, возвращенное memcpy — значение to.

Вот пример того, как Вы могли бы использовать memcpy, чтобы копировать содержимое массива:

Если длина from — больше чем size, то strncpy копирует только первые size символов.

Если длина from меньше чем size, то strncpy, копирует все from, сопровождая его достаточным количеством пустых символов, чтобы получить всего size символов. Это редко полезно, но зато определено в соответствии c ANSI стандартом.

Поведение strncpy неопределено, если строки накладываются.

Использование strncpy в противоположность strcpy — способ избежать ошибок в отношении соглашения о записи после конца размещенного пространства для to. Однако, это может также сделать вашу программу намного медленнее в одном общем случае: копирование строки, которая является возможно малой в потенциально большой буфер. В этом случае, size может быть большой, и когда это, strncpy будет тратить впустую значительное количество времени, копируя пустые символы.

Илон Маск рекомендует:  Web браузер собственными руками

Если malloc не может зарезервировать пространство для новой строки, strdup возвращает пустой указатель. Иначе она возвращает указатель на новую строку.

Например, эта программа использует stpcpy, чтобы конкатенировать «foo» и «bar» и печатает «foobar».

Поведение неопределено, если строки накладываются.

Эквивалентное определение для strcat было бы:

Функция strncat могла быть выполнена примерно так:

Вот пример, показывающий использование strncpy и strncat. Обратите внимание, как вычислен параметр size, в обращении к strncat, чтобы избежать переполнять символьный массив buffer.

Вы можете использовать функции в этого раздела, чтобы выполнить сравнение на содержимое строк и массивов. Также как для проверки равенства, эти функции могут использоваться как функции упорядочения для операций сортировки. См. Главу 15 [Поиск и сортировка].

В отличие от большинства операций сравнения в C, строковые функции сравнения возвращают значение отличное от нуля, если строки — не эквивалентны. Знак значения указывает относительное упорядочение первых символов в строках, которые — не эквивалентны: отрицательное значение указывает, что первая строка — «меньше» чем вторая, в то время как положительное значение указывает, что первая строка «больше».

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

Если содержимое двух блоков равно, memcmp, возвращает 0.

На произвольных массивах, функция memcmp обычно полезна для тестирования равенства. Обычно не имеет смысла делать байтовое сравнение упорядочения на не байтовых массивах. Например, байтовое сравнение на байтах, которые составляют числа с плавающей запятой не должно правдоподобно сообщить Вам что — нибудь относительно связи между значениями чисел с плавающей запятой.

Вы должны также быть внимательны при использовании memcmp, для сравнения объектов, которые могут содержать «дырки», типа дополнения, вставленного в объекты структуры, чтобы предписать требования выравнивания, дополнительного пространства в конце объединений, и дополнительных символов в концах строк, чьи длины меньше чем затребовано при размещении.

Содержимое этих «дырок» неопределено и может вызывать странное поведение при выполнении байтового сравнения. Для более предсказуемых результатов, выполните явное покомпонентное сравнение.

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

Если две строки равны, strcmp, возвращает 0.

Следствие упорядочения, используемого strcmp — то, что, если s1 является начальной подстрокой s2, то s1 «меньше чем» s2.

Имеются некоторые примеры, показывающие использование strcmp и strncmp. Эти примеры подразумевают использование набора символов ASCII. (Если используется некоторый другой набор символов скажем, расширенный двоично-десятичный код обмена информацией, взамен, то glyphs связаны с различными числовыми кодами, и возвращаемые значения, и порядок могут отличиться.)

В некоторых местах соглашения лексикографического упорядочения отличаются от строгого числового упорядочения символьных кодов. Например, в Испании большинство букв с диакритическими знаками, но буквы с диакритическими знаками не считаются различными символами в целях объединения. С другой стороны, последовательность с двумя символами «ll» обрабатывается как одна буква, которая объединена с «l».

Вы можете использовать функции strcoll и strxfrm (объявленные в заголовочном файле «string.h») чтобы сравнивать строки, использующие объединение упорядочивания соответствующее для данной местности. Стандарт, используемое этими функциями в частности может быть определено, устанавливая стандарт для класса LC_COLLATE; см. Главу 19 [Стандарты].

В стандартном расположении C, последовательность объединений для strcoll — таже что для strcmp.

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

Функция strcoll выполняет эту трансляцию неявно, чтобы делать одно сравнение. А, strxfrm выполняет отображение явно. Если Вы делаете многократное сравнение, используя ту же самую строку или набор строк, то более эффективноиспользовать strxfrm, чтобы трансформировать все строки только один раз, и впоследствии сравнивать преобразованные строки strcmp.

Вот пример сортировки массива строк, с использованием strcoll, чтобы сравнить их. Фактический алгоритм сортировки здесь не написан; он исходит из qsort (см. раздел 15.3 [Функции сортировки массива]). Работа кода показанного здесь, говорит, как сравнивать строки при их сортировке. (Позже в этом разделе, мы покажем способ делать это, более эффективно используя strxfrm.)

Поведение неопределено если строки to и from перекрываются; см. раздел 5.4 [Копирование и конкатенация].

Возвращаемое значение — длина всей преобразованной строки. На это значение не воздействует значение size, но если она большее чем size, это означает, что преобразованная строка полностью не поместилась в массиве to.

Чтобы получать целую преобразованную строку, вызовите strxfrm снова с большим массивом вывода.

Преобразованная строка может быть больше чем первоначальная строка, а может также быть более короткой.


Если size — нуль, никакие символы не сохранены в to. В этом случае, strxfrm просто возвращает число символов, которое было бы длиной преобразованной строки. Это полезно для определения какую строку зарезервировать. Не имеет значение, что будет в to, если size — нуль; может быть даже пустой указатель.

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

Этот раздел описывает библиотечные функции, которые выполняют различные виды операций поиска в строках и массивах. Эти функции объявлены в заголовочном файле «string.h».

Она подобна strstr, но needle и haystack байтовые массивы, а не строки с нулевым символом в конце. needle_len — длина needle, а haystack_len — длина haystack.

Эта функция — расширение GNU.

В программах довольно часто возникает потребность сделать некоторые простые виды лексического и синтаксического анализа, типа разбивания командной строки в лексемы. Вы можете делать это функцией strtok, объявленной в заголовочном файле «string.h».

Строка, которую нужно разбивать передается как newstring только при первом обращении. Функция strtok использует ее, чтобы установить некоторую внутреннюю информацию о состоянии. Последующие обращения, чтобы получить дополнительные лексемы из той же самой строки обозначаются передачей пустого указателя как аргумента. Вызов strtok с другим не-пустым символом newstring повторно инициализирует информацию о состоянии. Гарантируется, что никакая другая библиотечная функция (которая смешала бы эту внутреннюю информацию о состоянии) никогда не вызовет strtok.

Аргумент delimiters — строка, которая определяет набор разделителей, которые могут окружать извлекаемую лексему. Все начальные символы, которые являются элементами этого набора, отбрасываются. Первый символ, который не является элементом этого набора разделителей, отмечает начало следующей лексемы. Конец лексемы находится, как следующий символ, который является элементом набора разделителей. Этот символ в первоначальной строке newstring зменяется пустым символом, и возвращается указатель на начало лексемы в newstring.

При следующем обращении к strtok, поиск начинается со следующего символа после того который отмечен концом предыдущей лексемы. Обратите внимание, что набор разделителей не должен быть тем же самым при каждом вызове strtok.

Если конец строки newstring достигнут, или если остаточный член от строки состоит только из символов — разделителей, strtok, возвращает пустой указатель.

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

Строка, на которой Вы действуете, могла быть и константой. И, когда strtok попробует изменять ее, ваша программа получит фатальный сигнал о записи в память распределенную тоько для чтения. См. раздел 21.2.1 [Сигналы ошибки в программе].

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

Функция strtok не повторно используема. См. раздел 21.4.6 [Неповторная входимость], для обсуждения где и почему повторная входимость важна.

Вот простой пример, показывающий использование strtok.

Почему функции памяти, такие как memset, memchr. находятся в string.h, но не в stdlib.h с другими функциями mem? — c

Интересно, почему такая функция как:
-memset
-memmov
-memchr
-memcpy

Exist в файле заголовка string.h, но не в файле stdlib.h, где есть другие стандартные функции памяти в качестве распределения динамической памяти: malloc, calloc, realloc, free.

Может быть, было бы лучше объединить их в один заголовок? Что вы думаете об этом? Я не понимаю, почему один набор функций памяти отделен от других и существует в заголовке строки (string.h).

    3 3
  • 4 окт 2020 2020-10-04 18:18:02
  • Anonymous

3 ответа

В Pre-Standard C эти функции были действительно определены где-то в другом месте, но ни в stdlib.h ни в любом другом стандартном заголовке, а в memory.h . Он все еще может существовать в вашей системе, он, безусловно, по-прежнему работает на OS X (на сегодняшний день).

memory.h в OS X 10.11 (без заголовка лицензии):

Весь файл находится только #include ing string.h , чтобы сохранить обратную совместимость с программами Pre-Standard C.

  • 4 окт 2020 2020-10-04 18:18:04
  • Leandros

Я бы не думал о функциях string.h как функции «памяти». Вместо этого я бы назвал их «функциями массива», поскольку они работают с данными, содержащимися в последовательности памяти. Напротив, malloc (и другие) фактически предоставляют службы памяти, такие как распределение, а не манипулирование данными в области памяти.


В частности, функции в string.h не заботятся ни о каком распределении или освобождении памяти или о какой-либо форме управления памятью. Даже такая функция, как char * strerror(int) , которая, как представляется, создает целую новую строку, не выполняет никаких распределений, потому что возвращаемое значение на самом деле является статически выделенной строкой. Другие функции могут возвращать указатель на блок памяти, но на самом деле это всего лишь один из их параметров (например, memcpy ). Или они возвращают указатель на начало подстроки ( strtok ) или целое число, представляющее сравнение ( memcmp ).

С другой стороны, stdlib.h также не относится к памяти. Конструкция stdlib.h заключается в предоставлении операций общего назначения, которые, вероятно, потребуют большого количества программ. Функции памяти просто являются примерами таких фундаментальных операций. Однако другие функции, такие как exit и system , также являются хорошими примерами, но не относятся к памяти.

Теперь в stdlib.h есть некоторые функции, которые ИМО мог бы быть помещен в string.h , в частности различные функции преобразования ( mbstowcs , wcstombs , atoi , strtod и т.д.), и возможно, даже функции bsearch и qsort . Эти функции следуют тем же принципам, что и функции string.h (они работают с массивами, не возвращают вновь выделенные блоки памяти и т.д.).

Но с практической точки зрения, даже если было бы разумно объединить функции mem* с функциями malloc , realloc , calloc и free , стандартная библиотека C никогда не будет для такой реорганизации. Такое изменение, безусловно, нарушит код. Кроме того, stdlib.h и string.h существуют так долго и являются как полезными, так и фундаментальными библиотеками, что изменения, вероятно, нарушат большинство (или, по крайней мере, много) C-кода.

Memchr   memset работа с байтами в массивах

55788 просмотра

11 ответа

611 Репутация автора

1) Как и при использовании memset, мы можем инициализировать только несколько значений индекса целочисленного массива до 1, как указано ниже?

Ответы (11)

12 плюса

5113 Репутация автора

Короткий ответ, НЕТ.

Длительный ответ, memset устанавливает байты и работает для символов, потому что они являются одиночными байтами, но целые числа не являются.

35 плюса

88767 Репутация автора

Нет, вы не можете так использовать memset() . В manpage говорится (внимание мое):

memset() Функция заполняет первые n байты из области памяти , адресуемый параметром s с постоянным байтом c .

Так как int обычно это 4 или 8 байтов, это не сократит его.

Если вы ( неправильно !! ) попытаетесь сделать это:

то первые 6 int с в массиве будут фактически установлены на 0x01010101 = 16843009.

Единственный раз, когда действительно приемлемо писать над «блобом» данных с небайтовыми типами данных, является memset(thing, 0, sizeof(thing)); «обнуление» всего struture / массива. Это работает, потому что NULL, 0x00000000, 0.0, все полностью нули.

Решение состоит в том, чтобы использовать for цикл и настроить его самостоятельно:

3 плюса

5937 Репутация автора

Третий аргумент memset — размер байта. Таким образом, вы должны установить общий размер байта arr[15]

Однако, вероятно, вам нужно установить значение 1 для целых элементов в обр. Тогда вам лучше установить в цикле.

Потому что memset() установить 1 в каждом байте. Так что это не ваш ожидаемый.

Автор: mattn Размещён: 25.06.2013 03:55

плюса

2034 Репутация автора

Поскольку никто не упоминал об этом .

Хотя вы не можете инициализировать целые числа со значением 1 с помощью memset, вы можете инициализировать их со значением -1 и просто изменить свою логику для работы с отрицательными значениями.

Например, чтобы инициализировать первые 6 номеров вашего массива -1 , вы бы сделали

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

1 плюс

255553 Репутация автора

Нет, вы не можете [портативно] использовать memset для этой цели, если только не требуется целевое значение 0 . memset обрабатывает область целевой памяти как массив байтов, а не массив int s.

На самом деле основан довольно популярный хак для заполнения области памяти повторяющимся шаблоном memcpy . Он критически полагается на ожидание memcpy копирования данных в прямом направлении

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

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL