++recoveryModePublicationCount; // счетчик увеличивается уже n-й раз
Дорогой читатель, если ты только начинаешь изучать С (не С++) или хочешь немного систематизировать знания (из уровня прописных истин) тогда эта статья возможно лишит тебя счастья прострелить себе ногу пару раз и будет полезной.
Размерность массивов
Известно, что вся динамическая память в С выделяется с помощью трех функций из stdlib – malloc, calloc, realloc. Основной проблемой тут становится факт, что все выделяемые размеры памяти считаются в байтах, а не в количествах элементов.
И если начинать изучать С с более высокоуровневых языков, то обычно очень часто про это можно забыть, и потом не понятно, почему при вызове realloc массива, стираются данные в m-го поля в n-й структуре.
Типичный пример того, как делать нельзя:
int *array = malloc(10);
И тут понимаешь, что при записи в лучшем случае в 3-ю переменную (array[3] = 1;) ты получаешь SIGSEGV, и не понимаешь, что произошло.
Всегда выделение должно происходить по следующей схеме:
Type *array = malloc(sizeof(Type) * arraySize);
Т.е. правильно
int *array = malloc(sizeof(int) * 10);
Пример выше выделяет память на 10 элементов типа int в куче.
Realloc
Тут же возникает аналогичная проблема в realloc’e, где размер точно так же не умножается на sizeof(type).
Почти правильная сигнатура:
realloc(somePtr, sizeof(type) * newArrayCount);
Еще проблема realloc’a – в том, что многие забывают, что realloc может полностью перенести данный ему указатель в совсем другое место. Отсюда, необходимость принимать значение указателя, которое возвращает realloc:
newPtr = realloc(somePtr, sizeof(type) * newArrayCount);
При небольших изменениях размера newPtr и somePtr обычно совпадают, но true debug начинается тогда, когда они отличаются, и опять при попытке записи получаем SIGSEGV.
Для всех случаев с размерами массивов использую маленький макро, который сам подставляет sizeof от типа.
#define arraySize(className, size) (sizeof(className) * size)
таким образом создание массива выглядит в форме:
int *first = malloc(arraySize(int, 10));
а перераспределение как:
int *second = realloc(first, arraySize(int, 20));
При этом, если память переносится т.е. second != first, то все элементы из first копируются в second (), а first перестает существовать и стает невалидным указателем (но очень часто не nil, так что зануляйте указатели после free, для повторного использования).
Я для себя сделал еще два простых макро для аллокации одного обьекта и массива обьектов:
#define allocator(className) malloc(sizeof(className))
#define arrayAllocator(className, size) malloc(sizeof(className) * (size))
выглядит довольно неплохо, например:
someStruct *temp = allocator(someStruct);
или
someStruct *array = arrayAllocator(someStruct, 42);
Null pointers
Если долго программировать на С++, и вернутся в С, то можно открыть много нового для себя. Например то, что в C нету обьектного nullPtr, который выдает ошибку компиляции. В стандарте C определено, что null pointer – это указатель, который не указывает ни на какой обьект в программе, и определяется как макро
#define NULL ((void*)0)
– кастованой к типу указателей численной константы 0. Это в лучшем случае выдает warning о несоответствии типов. Всегда читайте warnings компилятора, если их нет – хорошо, если есть возможно один из них представляет корень зла. Из синтаксического сахара, обычно использую пару штук для указателей:
typedef void* pointer;
#define nil ((pointer) 0)
По моему pointer выглядит более концептуально чем void*.
Проверка на NULL
Обычно, когда работаешь в прикладной сфере программирования, с exception, не задумываешься об ошибках вообще. Когда проектировали unix, решили, что-бы не убивать программу целиком, при недостатке памяти возвращаем нулевой указатель, как просьбу немного почистить память. То есть malloc, calloc, realloc, возможно могут просто не выделить память (ну кончилась озу, что поделать), тогда они возвращают NULL (а я далее буду писать nil, т.к. мне он более мил)
int *array = arrayAllocator(int, 10);
if(a != nil) {
array[9] = 9;
doSome(array);
} else {
someCleanup();
}
если же попробовать разыменовать невалидный указатель (nil так же является невалидным) то программа получает SIGSEGV, или еще что-нибудь более интересное типа general protection fault. И тогда пограмма падает полностью. Если обрабатывать nil, то можно успеть задампить критичные даные например.
Приведение типов
Очень часто в примерах можно увидеть строки
int *array = (int*)malloc(sizeof(int) * 10);
Так вот, там приведение типов не нужно, т.к. malloc возвращает сырой указатель(void*), который автоматически является прообразом всех указателей. Любой указатель в С, например, int * — это тот же void* только с маленькой отметкой компилятора, о том, что его можно разыменовать в данный тип. Пусть даже это будет SomeStruct* — это все равно 4-х или 8-ми байтный void*. Проблемы с приведением типов(кастингом) возникают только тогда, когда указатель надо разыменовать и он разыменоввуется некорректно. Поэтому никогда не приводите типы разных структур друг к другу, если нет уверенности, как структуры распологаются в памяти. Если же она есть – использутеся direct cast, типа:
SomeStruct * arrayOfStructs = (SomeStruct*)arrayOfInts;
И тогда компилятор не выдаст даже warning, т.к. считает что вы понимаете, что вы делаете.
Memory management level advanced (custom allocators)
Крайне часто программисты становятся ленивыми, и забывают чистить за собой память, для этого придумали кучу интсрументов, как reference counting, RAII, но в C этого нету, по этому используются кастомные аллокаторы памяти, с усиленым контролем. Например ARP Pool или RPool, а на системном уровне обычно это ASLR – песочницы которые работают немного по другому принципу, но все же их можно отнести к custom allocators. В openBSD, или OSX например.
metaAlloc
Еще одно расширение RPool (описанным в ссылке выше) – metaAlloc – позволяет вести записи во время выделения памяти, чтобы понимать, где конкретно остались не освобожденные ресурсы.
Например
enablePool(RPool);
Int *array = metaAlloc(arraySize(int, 10), “Array for storing some indexes”);
double *somePointer = metaAlloc(arraySize(double, 1), "Some double pointer");
printerOfRAutoPool(RPool);
deleter(RPool, RAutoPool);
Выделенная память, если не освобождена то показывается в конце работы программы с подсказкой:
0 - 0x100104b00 [s: 8] (tuid: 149303) - Some double pointer
1 - 0x100105170 [s: 40] (tuid: 149303) - Int Array for storing some indexes
И сразу понятно, где конкретно не очищается память. В release моде, для того чтобы не тратить дополнительных ресурсов (память и время выполнения), просто отключается флаг, и metaAlloc превращается в обычный malloc.
Выводы
Всегда необходимо следить за размерностью выделяемой памяти, и помнить, что она в байтах. Сохранять результирующий указатель, который возращает realloc. Проверять указатели на NULL. Если необходимо отпарсить сложный открытый код на ошибки динамической памяти – использовать кастомные аллокаторы или чекеры.
P.S. Не претендую на гениальность, но возможно статья будет кому-нибудь полезной. Если у кого возникнут вопросы или идеи по использованию RPool, всегда буду рад помочь, чем только смогу.
Комментарии (59)
Ivan_83
27.04.2015 17:28+3«Когда проектировали unix, решили, что-бы не убивать программу целиком, при недостатке памяти возвращаем нулевой указатель, как просьбу немного почистить память.» — насколько я знаю, оно сейчас всегда возвращает не NULL, однако по факту память не выделяется до первого обращения, и выделяется по странично.
Можно написать:
void *ptr = malloc(1 * 1024 * 1024 * 1024);
а в top после выполнения будет всё ещё каких мегабайт 3-50 для данного процесса.
В итоге можно словить ошибку при записи в выделенную память, потому что фактически свободных страниц памяти в системе нет и она ничего не смапила.
А ещё во FreeBSD аллокатор можно настраивать, вот так отладка станет чуть легче:
#ifdef BSD /* BSD specific code. */
#include <malloc_np.h>
#ifdef DEBUG
const char *_malloc_options = «AJMPX»;
#endif
#endif
Подробности:
www.freebsd.org/cgi/man.cgi?query=malloc&apropos=0&sektion=3&manpath=FreeBSD+9.0-RELEASE&arch=default&format=html
Начиная с 10 версии опции по другому задаются:
www.freebsd.org/cgi/man.cgi?query=malloc&apropos=0&sektion=0&manpath=FreeBSD+10.1-RELEASE&arch=default&format=htmlZyXI
27.04.2015 22:29«Когда проектировали unix, решили, что-бы не убивать программу целиком, при недостатке памяти возвращаем нулевой указатель, как просьбу немного почистить память.» — насколько я знаю, оно сейчас всегда возвращает не NULL, однако по факту память не выделяется до первого обращения, и выделяется по странично.
Зависит от настроек. См /proc/sys/vm/overcommit_ratio. Для постоянного изменения используйте /etc/sysctl.conf.
Т.е. если нужно, то можно заставить ядро выделять память всегда. Ну и ядро обязательно выделит вам память, если невыделение заставит его превысить настройки.
ProstoTyoma
27.04.2015 19:29А что за выравнивание в 60 байт между contact1 и contactPointer на КДПВ?
StrangerInRed Автор
27.04.2015 19:38Первая картинка, которую нашел в гугле подходящей под тему. Извините, она не моя.
tshev
27.04.2015 21:01+4Проостите, но эти вещи знает любой хороший школьник, которые интересуется программированием.
namespace
28.04.2015 07:56+2К сожалению, нет. Нифига не знают этого школьники, этого даже подавляющее большинство выпускников отечественных вузов не знает, не то, что школьники. Ровно так же, как никто не знает, как устроен GC в Java, как определяются типы в питоне и тд.
tshev
02.05.2015 12:11Мм! На сколько я понял, то вы говорите о КПИ и КНУ. Соглашусь.
Главная проблема в том, что студенту нельзя(трудно) объяснить, что есть список из «он должен»:
— «To know something about everything and everything about something»
— любить то, что он делает и стараться делать это лучше других
— уметь писать работающий код на бумаге
— знать математику и алгоритмы (благо, Дональд Кнут написал замечательную монографию)
— знать, как устроен современный компъютер и как это связано с ЯП, на котором он пишет
— уметь реализовывать протоколы и аглоритмыnamespace
02.05.2015 12:26Я сейчас говорю о большинстве технических вузов в принципе. Я сейчас ученик 10 класса, но со студентами очень часто общаюсь/дисктирую, это ужасно. Вы сейчас назвали супер клишейный список. Давайте по-порядку:
— полностью согласен;
— полностью согласен;
— бесполезнейший навык. я научился не делать банальных опечаток и прочей ерунды после первой пары vim over ssh сессий, а непосредственно написание кода приходит с опытом;
— 90% приложения это бизнесс-логика. я не думаю, что для программиста математика — это мастхев. да, линейную алгебру, вычислительную геометрию, графы знать (ориентироваться) надо в любом случае, а это уже, я думаю, дает достаточный аппарат, чтобы осваивать все остальное по-потребности; алгоритмы — это мастхев, разумеется;
— сильно зависит от области: будет полезно системному программисту на плюсах и крайне бесполезно для питониста, который разрабатывает веб-приложения;
— протоколы это, вероятно, круто, но тоже очень бесполезно. в теории, конечно, надо знать, как работает TCP, но я думаю, что ооочень маленькой доле программистов приходится в своей жизни писать свой протокол.tshev
03.05.2015 13:21+1Сам был 3 года назад школьником, — у меня всё началось с комбинаторики и в районе 6-7-го классов переросло в программирование.
Сначала хотел написать короткий комментарий о том, что у математика в голове, как правило, порядок, ибо он умеет раскладывать вещи по полочкам и называть сущности своими именами и никогда не скажет: «Эту задачу нельзя решить, ибо что я не нашел подходящей библиотеки».
В итоге получилась некая смесь крика души и эссе, которое скорее адресовано всему миру, нежели кому-то индивидуально
В общем случае программист переживает за такие вещи, как:
- алгоритмическая корректность
- расширяемость
- масштабируемость
- производительность
- эффективность
О сроках сдачи он не переживает до того помента, пока они не наступают.
Любой может научится пользоваться конкретной технологией(набором технологий) за 4 месяца на уровне достаточном, чтобы это занятие стало приносить прибыль.
Если это экономист, который научился пользоваться sklearn, ipython, pandas, scipy, numpy для анализа данных — отлично.
Если это владелец магазина, который понял, что существует nginx, mysql, php, html, css, javascript и Magento — пожалуйста; круто, — он сможет сократить затраты на Интернет-магазин и, наверное, разберется с тем, как его раскрутить, чтобы получать от него прибыл, а не дополнительные расходы.
Но после этого ни экономист, ни владелец уже Интернет-магазина, не стали программистами.
Если ты закончил музыкальную школу по классу фортепиано, — это не означает, что ты можешь называть себя пианистом.
Быть проофессионалом в определенной сфере деятельности — это тяжкий труд. Одним он приносит удовольствие, другим — нет. Я с улыбкой на лице объясню человеку все преймущества и недостатки метода "Backpropagation" не написав ни одной формулы, после чего на вопрос «Как мне им воспользоваться?», я сошлюсь на Matlab backpropagation, а не отошлю человека на github.
Но я с трудом удержусь от соблазна «дать в лицо человеку», который будет кричать, что его многопоточная программа работает и выдает ± нужный результат и ему плевать, что в его коде есть data-race. Я скажу: «Я предупредил, а дальше твое дело. Ты не прав и я сказал почему. Мне с такими, как ты, еще работать, — пойди и исправь.»
Длинное не лирическое отступление
Я ненавижу, когда программист на С++ использует голые циклы, явным образом управляет ресурсами. Я ненавижу наблюдать в коде std::map<std::vector<std::map>> collection. Я ненавижу, когда кто-то бросает исключения, вместо, того, чтобы просто «не сделать ничего». Я не люблю, когда используют find вместо lower/upper_bound при поиске элемента в отсортированном векторе. Я не люблю, когда слышу, что вектор из 400 000 элементов — это огромная коллекция. Я не люблю, когда пользовательские типы ведут себя, не подобно встроенным. Я не люблю, когда все деструкторы становятся виртульными. Я не люблю, когда под отдельную фунецию создают новый файл. Я не люблю, когда в цикле for пытаются в условие прерывания написать все, что только поместится. Я не люблю, когда лямбда-функция занимает больше двух строк. Я не люблю, когда в заголовочных файлах в глобальном простанстве имен используется директива using. Я люблю С++, — он говорит мне: «ты берешь на себя ответственность за то, что ты пишешь, а я даю тебе тот уровень контроля, который ты захочешь и довольно дешевый способ построения абстракций». Я люблю С потому что он — не большой язык программирования, который дает возможно быть близким к железу, не прибегая к Ассемблеру. Мне не нравится, когда люди говорят, что ядро Linux должно быть переписано на C. Мне не нравится, когда кричат «мы должны писать на языке x вместо, языка y, ибо у языка y в стандартной библиотеке есть нужная функция». Мне не нравится, когда говорят, что мы должны использовать Rust потому что на языке С писать код трудно. Я ненавижу, когда говорят: «Хотите сделать фичу x, — пользуйтесь только технологиями множества Y».
И еще одно:
«Ненавижу, когда обижают одно из не безизвестных творений Никола Вирта». Возможно, этому языку программирования не стоит учить студентов на протяжении целого года, но 2-х часовой рассказ о его влиянии на языки программирования не повредит никому.
— Почему я хочу иметь дело с человеком, который знаком с математикой помимо всего прочего?
— Потому что в голове есть дисциплина. Я всегда смотрел на математику, как на приятную игру, в которой победить иногда бывает трудно. А, когда говорят о не нужности математики, — я Римскую империю.
Крик души
Меня трясет, когда я вижу Senjor Java-программиста, который знает только Java. Я морально готовлюсь к тому, что мне предложат написать HelloWorld, а потом заплюют, ибо для вывода сообщения я не создал отдельного класса с методом приветствия; жду еще рассказа о том, как хороша абстрактная фабрика(огромный кусок куда, который не делает ничего), о тысяче способов написать Синглтон, о том, как сказачно хорош Java EE, а Spring — вообще сказка, а те, кто пользуется PlayFramework — находятся на де, которое состоит из людей, которые любят RubyOnRails, но не согласны с его производительностью. А еще спросят о том перадаются ли параметры по значению, или по ссылке. Запоют песню о том, что нет указателей в Джаве и расскажут, что С++ — дрянь потому что он взял указатели из С. Потом сделают вывод, что мне нравится С++(так оно и есть, но это не мешает мне сказать то же самое о R, Python, Ruby, Rust и о некоторых других технологиях, свзязанных с этими языками… я умолчу о том, что для некоторых, задача я предпочту воспользоваться Java || Scala, ибо этот человек уже для себя решил, что Java я не знаю и ничего не смыслю в проэктировании, — да он ведь прочитал хорошую книгу, которую на Амазоне оценили в 4.5 звезды из 5, ее оценили бы в 5 балллов, но всё испортили такие зловреды, как я. Я ведь не говорю, что паттернам нет места в реальном мире, я только хочу, чтобы поменьше людей превращали их в религию).
P.S.:
Студент Стенфорда, получивший максимальный балл по всем предметам тоже не всегда может называть себя программистом. Почему? Он может вести себя, как физик, который недоученный физик, который знает все законы такого, что происходит в мире. Вот только есть одна беда: в физике остались вопросы, на которые не даны корректные ответы. На мой вгзляд самое страшное, что может случится с человеком — это утрата любопытства.
Вместо вывода
Вы можете подумать, что я слишком доволен собой(это не так, — у меня к себе всегда есть притензии). Русский язык слишком не однозначен. Дорогой читатель этого сообщения, вы можете меня не правильно понять, я мог описать свои мысли в слишком резкой форме. Я могу быть не правым; вы тоже можете ошибаться в своих убеждениях.
Мне нравится то, что я делаю и то. То, чему уделяю внимание, я считаю важным.
Как сказал маленький паж:
«Я не волшебник, я ещё только учусь, но любовь помогает нам делать настоящие чудеса.»
namespace
03.05.2015 13:36+1А давайте-ка вы это запостите на днях в
профильный Javaкакой-то хаб, я думаю, что это очень крутой текст.tshev
03.05.2015 19:10Жду комментарий комментариев:
— «очередной программист учит нас тому, как надо жить»
— «пустая болтология»
— «автора, наверное, кто-то в детстве очень сильно обидел»
Комментарию не хватает ссылок на первоисточники. А если их приводить, тогда будут не лестно упомянуты многие компании, в том числе Google(вдруг я там захочу работать), доклады из различных конференкций. А то, что касается outsource-компаний, работающих в Украине — это отдельная боль.
P.S:
Вы можете прочитать о немного другой точке зрения.
StrangerInRed Автор
03.05.2015 14:14Знаете, вся эта машина рождающая недо-программистов угнетает таких маленьких людей, как мы с вами. Которые собираются в топиках о С, чтобы поговорить про теплом ламповы old-school программирование. Люди которые всегда хотят знать как это работает. Именно по этому gnu/linux и open-source набирает популярность. Потому что ты как минимум можешь посмотреть, как это работает. Сейчас это все больше приобретает уровень «колонии прокаженных». «О, ты программируешь на С, да еще и с указателями, больной ублюдок». И всё близится тенденция из типа «Программирую на ассемблере за еду».
tshev
03.05.2015 18:52Почему угнетает?
В любой профессии есть люди, которые выполняют работу профессионально и те, которые никогда не упустят возможности полениться.
Не стоит никого обижать, называя «недопрограммистами». Просто компаниям стоит тщательнее отбирать сотрудников. Эти недопрограммисты могут качественно вести переговоры с заказчиком, оформлять финансовые бумаги, объяснять простым языком то, что вы бы объяснили математической моделью.
На мой взгляд, такой должности внутри компании сущестовать не должно (тестовое задание на hackerRank, а после успешного выполнения техническое интервью + испытательный срок(если возникает потребность)).
«Программирую на ассемблере за еду», — никогда не любил эту шутку.StrangerInRed Автор
03.05.2015 19:08Как же я ненавижу разницу между alt+< — в mac и windows. Вместо того, чтобы перейти на слово назад, ты переходишь назад в хроме.
Шутка про асм и еду уже скорее жестокая реальность. Я последний раз например видел вакансию с asm год назад. Недопрограммистами я считаю не тех людей, которые зная экономику например выучили какой нибудь яп. А под людьми которые пишут реальные проекты, не зная background того, что они используют, хотя бы в отдаленной теории. И да, использование низкоуровневых языков считается именно признаком идиотизма. Т — тенденции. Никто не пишет на С, т.к. это очень сложно. Родился мильон прослоек из с-подобных языков с ооп, так называемыми «легкими потоками». Которые просто легче понимать (якобы).zodiac
03.05.2015 20:28И да, использование низкоуровневых языков считается именно признаком идиотизма.
Вы ведь сейчас просто очень неудачно пошутили?StrangerInRed Автор
03.05.2015 23:22Это не шутка, это тенденции. Хабр да и не только он полон комментариев «Мне пришлось выучить X потому что он подходит под мою задачу». Все больше стало X, Y, Z, языков и т.д.
sheknitrtch
27.04.2015 21:03+7Задача: найдите ошибку в следующем макросе:
#define arraySize(className, size) (sizeof(className) * size)
Ответ:Возьмём пример:
int size = arraySize(double, 5 + 5); // Эта строчка превратиться в int size = (sizeof(double) * 5 + 5);
То есть, в макросе не хватает скобочек вокругsize
.
Вот вам ещё один способ выстрелить в ногу ;)zodiac
27.04.2015 21:43+6Т.е. правильно
int *array = malloc(sizeof(int) * 10);
Лучше так:
int *array = malloc(sizeof(*array) * 10);
чтобы не иметь проблем, если вдруг изменится тип array.
jcmvbkbc
28.04.2015 01:02+2В стандарте C определено, что null pointer – это указатель, который не указывает ни на какой обьект в программе, и определяется как макро
#define NULL ((void*)0)
– кастованой к типу указателей численной константы 0
Первая половина — правда, вторая — отсебятина.
Во-первых NULL по стандарту — implementation-defined, во-вторых в приличных компиляторах он определяется как
#define NULL 0monah_tuk
28.04.2015 04:24В каких таких компиляторах? Это часть stdlib. С тем же GCC/Clang я могу как минимум, newlib, glibc, eglibc, uclibc, musl и т.д. использовать. Как они определят, так и будет. Для C++ обычно определено так, как вы написали. Временами используется комиляторо-специфичная константа, типа __null.
Вот пример из libio.h (часть glibc):
#ifndef NULL # if defined __GNUG__ && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8)) # define NULL (__null) # else # if !defined(__cplusplus) # define NULL ((void*)0) # else # define NULL (0) # endif # endif #endif
Более того, условная проверка на объявление NULL наводит на мысли, что другие библиотеки могут переопределить NULL, если потребуется. К примеру, libstdc++ может взять и определить так:
#define NULL nullptr
Но что это implementation-defined я 100% согласен, а выдержки выше ещё и подтверждают.jcmvbkbc
28.04.2015 04:52Это часть stdlib. С тем же GCC/Clang я могу как минимум, newlib, glibc, eglibc, uclibc, musl и т.д. использовать
Можете. Однако, например, при использовании glibc и uclibc определение приходит из gcc, который устанавливает свой stddef.h.monah_tuk
28.04.2015 06:02выдержка из stddef.h Gcc 4.8:
#if defined (_STDDEF_H) || defined (__need_NULL) #undef NULL /* in case <stdio.h> has defined it. */ #ifdef __GNUG__ #define NULL __null #else /* G++ */ #ifndef __cplusplus #define NULL ((void *)0) #else /* C++ */ #define NULL 0 #endif /* C++ */ #endif /* G++ */ #endif /* NULL not defined and <stddef.h> or need NULL. */ #undef __need_NULL
Так что для Си будет именно ((void*)0), для C++ будет или __null или 0.
monah_tuk
28.04.2015 04:10+1Мне кажется, что это место нужно чуть раскрыть:
newPtr = realloc(somePtr, sizeof(type) * newArrayCount);
а то многие делают так:
somePtr = realloc(somePtr, sizeof(type) * newArrayCount);
Хотя и в секции про возвращаемый NULL сказано, что realloc может вернуть его, но бывает воедино эту информацию не собирают. Плюс, если realloc вернул NULL, то somePtr останется валидным, но при такой схеме он затирается. Как следствие: утечка памяти. На больших машинах не так актуально, но вот в embedded ооооочень.
m08pvv
28.04.2015 10:22Если обрабатывать nil, то можно успеть задампить критичные даные например.
Главное, чтобы не потребовалось выделять память в коде, который дампит, а для этого (если действительно что-то критичное) надо выделять нужную для этого память сразу после запуска программы. Например, как это делает CLR.StrangerInRed Автор
28.04.2015 12:58Очень даже отличный совет для самоманипуляций с данным. Но при записи в файл тут становится вопрос, в чей области ответсвенности это — программиста или OS.
m08pvv
28.04.2015 13:10Суть не в том, чья ответственность, а в том, что надо быть начеку и если, например, словили StackOverflowException, то не стоит в его обработчике вызывать рекурсивный обход дерева с записью в файл.
StrangerInRed Автор
28.04.2015 13:32Ну это не C#, и StackOverflowException не словишь. Можно словить SIGSEGV, но у меня на рабочей макоси обработчик его не ловит, и проложение умирает сразу же (если это stack overflow). А вот sigsegv при записи в адресс 0x1 ловит и обрабатывает.
Код примера#include <signal.h> #define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) static void handler(int sig, siginfo_t *si, void *unused) { printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr); printf("Implements the handler only\n"); //exit(EXIT_FAILURE); } void overflow() { byte buffer[1 * 1024 * 1024 * 1024] = {}; // byte buffer2[1 * 1024 * 1024 * 1024] = {}; printf("Must be overflow\n"); } int main(int argc, const char *argv[]) { struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) handle_error("sigaction"); { overflow(); // если закомментировать здесь обработчик работает корректно int *i = (int *) 0x1; *i = 200; } }
m08pvv
28.04.2015 13:48Про StackOverflowException я для примера написал.
Да и в Вашем примере компилятор может безболезненно вырезать
и заинлайнитьbyte buffer[1 * 1024 * 1024 * 1024] = {};
printf("Must be overflow\n");
m08pvv
28.04.2015 15:26И, да, StackOverflowException можно было ловить только до .Net Framework 2.0. Повторюсь, его я привёл исключительно как пример, не очень удачный.
kloppspb
Почему malloc(кол-во*sizeof), а не calloc(кол-во, sizeof)? Тем более что во втором случае память заведомо обнуляется.
StrangerInRed Автор
Очень редко наблюдаю использования calloc. Он например медленнее чем malloc, да и редко требуется обнулять память, чтобы его использовать. А так да, конструкция схожая.
Singerofthefall
Он может быть медленнее, но не обязательно — при вызове calloc ОС может найти для вас участок памяти нужного размера, который уже занулен.
GarryC
А каким образом ОС может найти уже содержащий нули блок без проверки?
Если бы я писал ОС, я бы так делать не стал, обнулить заново не медленнее, зато универсально.
StrangerInRed Автор
Скорее всего как и для malloc, создается страница 4 КБ размером, и из нее берутся для calloc маленькие куски. Или несколько страниц подряд и большие.
themiron
нет. calloc это malloc + memset
pfactum
Нет, по крайней мере в Линуксе это не так. Попробуйте сравнить по скорости calloc и malloc+memset.
themiron
зачем? если код calloc() и malloc() в *libc говорит сам за себя?
приводит к следующим сисколам (c mmu):и, если уж сравнивать, в каких условиях? с какими размерами?
p.s
разница будет только в memset, в худшем случае.
themiron
может быть где-то и ищет, но в linux kzalloc использует __GFP_ZERO флаг аллокации, по которому происходит банальное зануление выделенного блока
themiron
он медленнее во всех *libc ровно на memset(result, 0, size), для юзерспейса обычно несущественно.
OC тут не при чем
StrangerInRed Автор
Я бы не был так уверен в этом, т.к. например та же OSX по идее форсит malloc в calloc, и всегда выделяет чистую память. И например в ней разницы по времени между malloc и calloc может вовсе не быть
1101_debian
Меряли или так просто?
StrangerInRed Автор
Такое себе умозрительное заключение. В при инициализации как я помню из курса микроконтроллеров хранится треш. Собственно выделение памяти — это просто маппинг области в озу. А для зануление нужно еще пройтись по мусору нулями. Т.е. дополнительная операция. А вообще должно зависить от ОС. Потестю завтра, по заявкам, так сказать. Только парк осей не большой чтобы тестить. Давайте распределимся, если захотите я могу потестить в OSX и Windows, а с Вас Linux, если угодно, конечно же. А потом сравним результаты.
1101_debian
Не вижу в этом абсолютно никакого смысла, если честно. Чисто технически — вероятно, медленнее: вызов двух функций вместо одной + цикл по всем элементам (цикл ли?). Но думается мне что это «медленнее» далеко не самое узкое место программы, а потому использовать malloc over calloc из соображений производительности — premature optimization, не иначе.
StrangerInRed Автор
Ну например, это играло роль, когда контроллеры были теплыми, ламповыми и 250-мегагерцовыми. Сейчас это совсем даже не узкое место. Скорее дело привычки наверное. По сравнение со структурами, ооп например очень сильно просело в быстродействии (тот же obj-c). Так что да, это далеко не самое узкое место.
1101_debian
Что, простите?
StrangerInRed Автор
Говорю быстродействие просело. objc построенный на message send методе ужасно медленный по сравнению с directly structures.
hardex
«directly structures» это где-то из категории "I accidentally 93MB of .rar files"?
kloppspb
Заглянул одним глазом в исходники glibc 2.19 — calloc() там вовсе не сводится к malloc()/memset()…
Тесты можно погонять, но смысл? Я, например, имею обыкновение в сишностях использовать GC, а это уж всяко возможную микроразницу перекроет.
А обнулять — тоже, наверное, из разряда привычек, как явная инициализация. Да и для дебага полезно. Хотя, иногда полезней каким-нибудь DEADBEEF заливать, но это уже другая история :)
themiron
разве? sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;hb=HEAD#l3174
дальше — только оптимизации, когда нет необходимости занулять всё, либо нужно занулить только часть.
kloppspb
А, ну да, раскрыл второй глаз повнимательней — увидел :)
pfactum
В Линуксах вызов calloc может привести к маппингу всего выделяемого адресного пространства на заранее зануленную страницу (одну) с последующим COW при модификации выделенных страниц.
themiron
да, может. а может и не привести, о чем я писал выше
Singerofthefall
Поддерживаю вопрос. В догонку — не знаю, актуально ли это до сих пор, но линукс при вызове malloc на самом деле не выделяет вашему приложению никакой реальной памяти. Реальное выделение произойдет только когда вы к этой памяти обратитесь, поэтому даже получив ненулевой указатель можно было впоследствии словить out of memory.
StrangerInRed Автор
Не знаю как в Linux, но в sandboxed системах как osx, open/free BSD память выделяется большим куском перед началом работы, (100 МБ например) а потом просто мапятся указатели через mmap, судя по всему, как Вы и писали. (https://ru.wikipedia.org/wiki/Mmap)
aml
Актуально. Выделение памяти в куче использует системный вызов sbrk, который просто сдвигает доступный приложению предел адресов. Т.е. если malloc не смог найти свободный кусок памяти в куче, он её отращивает через sbrk. А когда процесс полезет в ту память первый раз, ядро поймает page fault и отмапит физическую память на эти адреса. Если при этом не найдётся свободной памяти и не удастся выгнать кого-нибудь из памяти, то включится страшный oom killer и будет искать, кого бы принести в жертву. Это уже другая история.