Страницы

Поиск по вопросам

Показаны сообщения с ярлыком malloc. Показать все сообщения
Показаны сообщения с ярлыком malloc. Показать все сообщения

пятница, 28 февраля 2020 г.

Заполнение структуры С

#c #struct #malloc


Суть такая:
имеется структура:

typedef struct {
    char * brand;
    int year;
} vehicle;


Выделяю память под неё так:

vehicle * car = malloc(sizeof(vehicle));


Если делать так:

car->brand = "Dodge";
car->year = 2007;
printf("%s %d", car->brand, car->year);


То всё работает, но если попытаться вводить с клавиатуры так:

printf("\nВведите марку авто: ");
scanf("%s", car->brand);
printf("\nВведите год выпуска авто: ");
scanf("%d", &car->year);


То будет 

Segmentation fault: 11


После ввода строки.

Изучаю только, просьба не кидаться, ибо разбираюсь пока в указателях.
    


Ответы

Ответ 1



printf("\nВведите марку авто: "); scanf("%s", car->brand); Вы читаете куда-то в какое-то неопределенное место в памяти... Вам нужно выделить место заранее - или, например, typedef struct { char brand[50]; int year; } vehicle; или выделять динамически - car->brand = malloc(50); scanf("%s", car->brand); Понятно, что 50 я взял "с потолка", вы должны брать разумные значения и при считывании строки обеспечить отсутствие переполнения буфера. Так что лучше воспользоваться функцией fgets, тем более что она считывает любую строку - в том числе с пробелами, чего не обеспечивает ваш способ ввода.

воскресенье, 9 февраля 2020 г.

Зачем нужен аргумент malloc в C?

#c #malloc #heap


Я начал изучать си по одной книге. Там был такой пример:

#include 
#include 
#include 

int main(int argc, char const *argv[]){
    char *char_ptr;
    int *int_ptr;
    int mem_size;

    mem_size = 1;
    printf("\t[+] allocation %d bytes of memory on the heap for char_ptr\n", mem_size);
    char_ptr = (char *) malloc(mem_size);
    strcpy(char_ptr, "This is memory is located on the heap");
    printf("char_ptr (%p) --> '%s'\n",char_ptr,char_ptr);
}


Тут я выделяю 1 байт для кучи. Потом записываю туда текст "This is memory is located
on the heap". Я думал, что будет ошибка, ткк для текста нужно больше чем 1 байт. Но
мне вернулся такой результат:

[+] allocation 1 bytes of memory on the heap for char_ptr
char_ptr (0x55e250f6c670) --> 'This is memory is located on the heap'


Из этого примера у меня один вывод: Куча увеличилась сама? Но если она увеличала
объем, то зачем нужен аргумент malloc()?
Можете объяснить почему это работает?
    


Ответы

Ответ 1



Код - неправильный. Нет, выделенный блок памяти не увеличился. Вы пишете за границы блока, так что у вас в коде - неопределенное поведение. Ошибка в таких случаях не гарантируется. Подробнее: Я думал, что будет ошибка Чтобы ошибка была, компилятор должен сгенерировать какой-то код, проверяющий выход за границы выделенного блока памяти. В общем случае это - нетривиальная задача. К тому же, программы с такими проверками работали бы медленнее. Поэтому компиляторы (с настройками по умолчанию) обычно не добавляют подобные проверки. В свою очередь, стандарт не требует наличия этих проверок. Вместо этого, там написано, что выход за границы массива или выделенного блока памяти вызывает неопределенное поведение. Это означает, что программа может вести себя как угодно. Обычно в таких ситуациях, как ваша, программа либо изменяет значения несвязанных, но находящихся рядом в памяти переменных; либо вылетает немедленно; либо вылетает через какое-то время в каком-то другом месте; либо вообще ничего не происходит. У некоторых компиляторов есть настройки, включающие защиту от подобных ошибок (-fsanitize=address у GCC и т.п.).

Ответ 2



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

воскресенье, 2 февраля 2020 г.

Как скопировать массив в динамический массив? (Си)

#c #массивы #указатели #malloc


Есть массив char str[] = "abcdef";

И динамический массив:
char *dstr = (char*)malloc(sizeof(char) * N);

Как скопировать str[] в dstr[] ? 

P.S. Заранее извиняюсь, если вопрос глупый.
    


Ответы

Ответ 1



Есть 3 способа, нативное копирование области памяти, специальной функцией копирования строк, посимвольный перебор массива и запись в dstr. memcpy(&str, &dstr, strlen(str) + 1); // Копирование области памяти strcpy(dstr, str); // Специальная функция для строки // Перебор массива циклом. int strsize = strlen(str) + 1; for(int i = 0; i < strsize ; i++) { dstr[i] = str[i]; } Имейте ввиду, в обоих случаях вы должны быть уверены что размер dstr больше на 1 чем длина строки str.

Ответ 2



В данном случае (создание копии строки, не содержащей двоичных нулей) проще всего (а если размер исходной строки не известен, то и не менее эффективно, чем использование malloc с последующими вызовами strlen, а затем strcpy или memcpy) вызвать strdup Например: #include ... char str[] = "abcdef"; char *dstr = strdup(str); Конечно, возвращаемый указатель стоит проверить на NULL (аналогично использованию malloc).

пятница, 31 января 2020 г.

Как правильно записать ф-цию malloc на Си?

#c #malloc


Нужно создать массив с помощью malloc. Как я прочитал есть два способа записи этой
функции:

    int * buffer = malloc (i * sizeof (int));


и

    int *buffer = (int *) malloc (i);


Но, когда я пытаюсь очистить выделенную память после того как использовал вторую
запись, то программа вылетает. В остальном же никакой разницы не заметил. Что я сделал
не так?
    


Ответы

Ответ 1



Во-первых, ваши два способа почти никак между собой не связаны: в первом фигурирует sizeof(int), а во втором его не видно. Поэтому не понятно, прочему вы их вообще привели вместе. Также, в соответствии с такой логикой составления "способов", не составит труда придумать еще пятьдесят два "способа записи этой функции", поэтому не ясно, почему ваш источник утверждает, что их всего два. Во-вторых, явное приведение типа возвращаемого значения malloc (как во втором способе) - ненужная, бессмысленная и вредная практика. (см. также DRY ниже) В-третьих, для выделения памяти под массив через malloc существует прекрасная идиома, построенная в соответствии с принципом DRY (Don't Repeat Yourself) int *buffer = malloc(i * sizeof *buffer); // Полагая, что `i` - требуемый размер массива Обратите внимание, что конкретное имя типа в этом варианте упоминается только один-единственный раз - в объявлении переменной. Именно (и только) в объявлениях и должны упоминаться конкретные типы. Остальной код, за пределами объявлений, в идеале, должен быть типонезависим (type-agnostic), т.е. не содержать никаких упоминаний имен типов вообще. Это не всегда достижимо, но в случае выделения памяти через malloc это вполне возможно.

Ответ 2



Вы выделили слишком мало памяти. При работе вы вышли за границы выделенной памяти, затерли служебную информацию диспетчера памяти и при освобождении памяти произошли неприятности... malloc выделяет указанное количество байт, так что если вам нужна память для N целых чисел - то выделяйте malloc(N*sizeof(int)).

вторник, 28 января 2020 г.

Почему нельзя передавать “malloc'нутые” значения из DLL?

#cpp #windows #malloc


Если в библиотечной функции выделяется блок динамической памяти, передача указателя
в загрузившее эту библиотеку приложение может привести к неприятным последствиям. С
чем это связано?

Я конечно понимаю, что у загруженной DLL будет свой менеджер памяти, и загружаться
она будет в область разделяемой памяти. Это значит, что переменные, объявляемые в ней
будут хранится в каком то своём стеке, а динамически выделяемая память расположена
в своей "куче". malloc внутренне, вроде как, хранит сколько он памяти выделил и где,
поэтому при вызове free, если менеджер памяти тот же, тоже знает расположение этой
информации и знает сколько памяти освободить. Таким образом, при передаче указателя
на память, выделенную в DLL, free не будет знать сколько памяти освободить.

В принципе, возвращаемый библиотечной функцией указатель можно использовать в любой
программе, запущенной на той же машине?
    


Ответы

Ответ 1



Вопрос сформулирован бессмысленно. Никакого "нельзя" тут не существует и никогда не существовало. Вы можете передавать в/из DLL любые указатели. Никаких противопоказаний для этого нет - это фундаментально необходимая возможность и отказываться от нее вам никто не предлагает. передача указателя в загрузившее эту библиотеку приложение может привести к неприятным последствиям Сама передача указателя НЕ может привести к неприятным последствиям. Попытка освобождения указуемой памяти на территории другого модуля может. У загруженной DLL может быть свой менеджер памяти. А может и не быть. Это зависит от конфигурации этой DLL. Это единственная деталь, которую нужно помнить: не предполагайте, что вы имеете право напрямую уничтожать объекты, выделенные внутри DLL. Вполне может оказаться так, что все объекты, созданные в DLL, должны передаваться обратно в DLL для уничтожения.

Ответ 2



Это возможно, но этого не делают. Причина может быть в том что библиотека и вызывающий ее модуль могут использовать разные аллокаторы, а потом вы воспользуетесь своим деаллокатором в вызывающем модуле? А если этот модуль - тоже библиотека, и указатель был передан выше? Эти библиотеки обладают своими методами отслеживания ресурсов, и глобальные объекты для выполения этих функций разные , если аллокаторы разные. Какие именно - зависит от компилятора и его версии\сборки. Даже если в конечном итоге все они вызывают GlobalAlloc\GlobalFree. Вызовы разных версий библиотеки времени исполнения обращаются к разным экземплярам фунций. А это, как история показала, в случае с майкрософтской библиотекой приводит к нарушению целостности кучи. Область памяти размещенной с помощью 8й студии не удалится вызовом функции из библиотеки 10й студии, и т.п. DLL была откомпилирована в 8 а программа - в 10й. Иногда даже номера сборок имели значение. Хорошим тоном является либо предоставление механизма создания и уничтожения объекта, либо, что предпочтительнее, выделение памяти вызывающим. Последний исключает всякую возможность того, что менеджер памяти из DLL будет конфликтовать с программой. .

суббота, 11 января 2020 г.

Выделение памяти под двумерный массив malloc

#c #массивы #указатели #malloc


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


Ответы

Ответ 1



Казалось бы, что может быть прямолинейнее, чем выделение динамически памяти под двумерный массив, а потому должно существовать одно решение. На самом деле можно по-разному реализовать данную задачу. Самый простой способ - это когда число колонок в двумерном массиве является константным значением, определенным либо с помощью директивы #define либо с помощью перечислителя. Тогда можно сразу же разместить динамически именно двумерный массив. Второй подход, это когда число колонок задается во время выполнения программы. В этом случае обычно выделяют несколько одномерных массивов. Сначала выделяют одномерный массив указателей с количеством элементов равных количеству строк в предполагаемом двумерном массиве, А затем для каждого указателя этого одномерного массива выделяют массив объектов базового типа, число элементов которого равно числу колонок в двумерном массиве. В этом варианте важно учитывать, что во время выделения массивов может возникнуть нехватка памяти, а потому желательно правильно удалить те массивы, которые были уже распределены, а иначе программа будет иметь неопределенное состояние. Третий подход заключается в использовании массивов переменной длины при условии, что C компилятор их поддерживает. Тогда число колонок и строк может быть задано во время выполнения программы, и можно выделить сразу же двумерный массив, а не массив массивов, как было описано в предыдущем абзаце. Также следует не забывать корректно освобождать выделенную динамически память под массивы, когда массивы уже не нужны. Ниже приведена демонстрационная программа, в которой показаны перечисленные способы выделения памяти для работы с двумерными массивами или их аналогами. #include #include #include #define COLS 10 char ( * first_allocate( size_t rows ) )[COLS] { char ( *s )[COLS] = malloc( rows * sizeof( char[COLS] ) ); return s; } void first_free( char ( *s )[COLS] ) { free( s ); } char ** second_allocate( size_t rows, size_t cols ) { char **s = malloc( rows * sizeof( char * ) ); if ( s != NULL ) { size_t i = 0; while ( i < rows && ( s[i] = malloc( cols * sizeof( char ) ) ) != NULL ) i++; if ( i != rows ) { for ( size_t j = i; j != 0; j-- ) free( s[j-1] ); free( s ); s = NULL; } } return s; } void second_free( char **s, size_t rows ) { for ( size_t i = 0; i < rows; i++ ) free( s[i] ); free( s ); } void third_allocate( size_t rows, size_t cols, char ( **s )[rows][cols] ) { *s = malloc( sizeof( char[rows][cols] ) ); } void third_free( size_t rows, size_t cols, char ( *s )[rows][cols] ) { free( s ); } int main( void ) { size_t rows = 2; size_t cols = 10; char ( *s1 )[COLS] = first_allocate( rows ); strcpy( s1[0], "first" ); strcpy( s1[1], "second" ); for ( size_t i = 0; i < rows; i++ ) printf( "%s ", s1[i] ); printf( "\n" ); first_free( s1 ); printf( "\n" ); char **s2 = second_allocate( rows, cols ); strcpy( s2[0], "first" ); strcpy( s2[1], "second" ); for ( size_t i = 0; i < rows; i++ ) printf( "%s ", s2[i] ); printf( "\n" ); second_free( s2, rows ); printf( "\n" ); char ( *s3 )[rows][cols] = NULL; third_allocate( rows, cols, &s3 ); strcpy( ( *s3 )[0], "first" ); strcpy( ( *s3 )[1], "second" ); for ( size_t i = 0; i < rows; i++ ) printf( "%s ", ( *s3 )[i] ); printf( "\n" ); third_free( rows, cols, s3 ); } Вывод программы на консоль будет следующим first second first second first second

пятница, 10 января 2020 г.

Как динамически создать массив, не зная количества его элементов? [дубликат]

#c #массивы #динамические_массивы #malloc


        
             
                
                    
                        
                            На этот вопрос уже даны ответы здесь:
                            
                        
                    
                
                        
                            Как создать динамический массив?
                                
                                    (3 ответа)
                                
                        
                                Закрыт 2 года назад.
            
                    
В цикле читаю некоторый блок данных по частям различных размеров. Требуется учитывать
размеры этих частей - думаю загонять их в массив. Как создать этот массив, если я не
знаю сколько частей будет? Возможно ли реализовать такое в языке С стандартными методами? 
    


Ответы

Ответ 1



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

понедельник, 6 января 2020 г.

Segmentation fault при обращении к указателю

#linux #c #указатели #malloc #memory_management


Решил для развития кругозора написать собственную имплементацию аллокатора памяти
под Linux, но при написании столкнулся со следующей проблемой. 

Вызов sbrk() возвращает указатель на начало выделенной памяти, затем я этот указатель
привожу к указателю на структуру Hdr. Но когда я пытаюсь задать значение переменной
по этому указателю, программа вылетает с Segmentation error. Чутьё подсказывает, что
я делаю глупость, ведь структуру нужно инициализировать прежде чем использовать, но
я не знаю как в данной ситуации правильно это сделать, учитывая, что память под структуру
уже выделена вызовом sbrk().

Заранее благодарен за любую помощь!

struct Hdr {
  int is_avaible;
  int size;
};

void *malloc(long memory_size) {
  void *memory_location;
  struct Hdr *current_location_struct;
  ...
  memory_location = sbrk(memory_size);
  if (memory_location == NULL)
    return;
  current_location_struct = (struct Hdr *)memory_location;
  current_location_struct->is_available = 0;
  ...
}

    


Ответы

Ответ 1



Давайте посмотрим как работает sbrk(). Схематично: void *sbrk(intptr_t increment) { void *heap_ptr = current_heap_end; if( increment > 0 ) { if( !expand_process_heap( increment ) ) { heap_ptr = (void *)-1; } } else if( increment < 0 ) { heap_ptr = reduce_process_heap( -increment ); } return heap_ptr; } Рассмотрим с конца. Если increment равен 0, то возвращаем текущий адрес конца кучи. Если отрицателен, то уменьшаем кучу и возвращаем адрес нового её конца. И если он положителен, то увеличиваем кучу и возвращаем старое его значение. Вот как раз пункт 3 ошибочно многими (не только начинающими программистами, но и книгописателями!) воспринимается как некий способ выделить память. Формально - да, как бы "память выделяется", как раз размером increment байтов. Но на деле эту память использовать по своему усмотрению напрямую нельзя! Потому что пытаясь туда что-то записать вы "сбиваете настройки" системному менеджеру памяти, что закономерно приводит к краху программы. Конечно, в случае реальной программы, а не в учебных примерах, в которых после использования sbrk() и вывода каких-то демо-значений в программе больше ничего не происходит. P.S. Кстати, если бы вы перед чтением сомнительной :) литературы прочитали man sbrk, то обратили бы внимание на следующее: On success, sbrk() returns the previous program break. (If the break was increased, then this value is a pointer to the start of the newly allocated memory). On error, (void *) -1 is returned, and errno is set to ENOMEM. P.P.S. Я уж не говорю о том, что заменить библиотечный malloc() своей реализацией - это не просто выстрелить себе в ногу, это усесться на связку гранат, облиться бензином и подорвать их.

Ответ 2



Что-то меня зацепило всеобщее недоверие к реализации своего malloc через sbrk. Вот очень простая (по сути дурацкая), но работающая программка, просто для демонстрации возможности работы с sbrk(). Поскольку вся группа функций (malloc/calloc/realloc/free) используется библиотечным софтом совместно, то реализовывать один лишь malloc нельзя. #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include static void *start; void * get_memstart () { return start; } // size multiples 16, allocated to bound multiples 16 struct bhdr { struct hdr { size_t bsize; uint cnt; char is_free; } hdr; char pad[(16 - sizeof(struct hdr)) & 15]; }; // each malloc returns new sbrk block void * malloc (size_t size) { static uint cnt = 0; const char *mtxt = "mmalloc\n"; if (write(1, mtxt, strlen(mtxt))) cnt++; if (!size) return 0; // increase space to mutilpe 16 for good bounding struct bhdr *p = (typeof(p))sbrk((15 + size + sizeof(*p)) & ~15); if ((long)p == -1L) return 0; if (!start) start = p; p->hdr.bsize = size; p->hdr.cnt = cnt; p->hdr.is_free = 0; return p + 1; } void * calloc (size_t nmemb, size_t size) { const char *mtxt = "mcalloc\n"; void *p = 0; if (write(1, mtxt, strlen(mtxt))) { p = malloc(nmemb * size); if (p) memset(p, 0, nmemb * size); } return p; } // mark early allocated block as free if bound is multiple 16 void free (void *ptr) { const char *mtxt = "mfree\n"; if (write(1, mtxt, strlen(mtxt))) if (!ptr || (long)ptr & 15) return; struct bhdr *p = (typeof(p))ptr; p[-1].hdr.is_free = 1; } // if new size greater than old size returns copy in new block void * realloc (void *ptr, size_t size) { const char *mtxt = "mrealloc\n"; if (write(1, mtxt, strlen(mtxt))) if (!ptr) return malloc(size); if ((long)ptr & 15) return 0; if (!size) { free(ptr); return (void *)-1L; } struct bhdr *p = (typeof(p))ptr; size_t tsz = p[-1].hdr.bsize; if (tsz >= size) return ptr; void *nptr = malloc(size); if (nptr) memcpy(nptr, ptr, tsz); return nptr; } int main (int ac, char *av[]) { struct bhdr b; printf("Hello, sbrk/malloc!\nbhdr: %d pad: %d hdr: %d\n", (int)sizeof(b), (int)sizeof(b.pad), (int)sizeof(b.hdr)); char *s = 0; size_t n; errno = 0; while ((fputs("Enter: ", stdout), getline(&s, &n, stdin)) > 0) { printf("read %s", s); if (*s == '.') { puts("New line mem"); free(s); s = 0; } } perror("stdin"); return fcloseall(); } Все "странности" кода с write() связаны с раздражающими (идиотскими) warnings gcc/g++ (g++.real (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0) вызываемым с ключами оптимизации (-O...) когда результат write не используется.

вторник, 31 декабря 2019 г.

Явный вызов конструктора для объекта

#cpp #malloc


Как создать объект в динамической памяти используя malloc()? Нужно ведь явно вызывать
конструктор, как это сделать?
    


Ответы

Ответ 1



Вам нужен placement new. Например, так: void* place = malloc(sizeof(Object)); Object* o = new(place) Object(); Не забудьте в конце вызвать и деструктор вручную: o->~Object(); и если нужно free(place); Документация: https://isocpp.org/wiki/faq/dtors#placement-new Для случая, когда у вас память выделена заранее, вам придётся позаботиться о выравнивании. Обычный указатель, который возвращает malloc, имеет выравнивание, достаточное для нормально объявленного класса, то есть, класса, определение которого не содержит alignas() (а также определение классов его полей), и значит, ничего больше делать не нужно. Если это (отсутствие alignas) не гарантировано, у вас есть два пути. Вы можете вместо malloc использовать aligned_alloc (или _aligned_malloc на старых версиях Visual Studio), который выдаёт malloc с нужным выравниванием: void* place = std::aligned_alloc(alignof(Object), sizeof(Object)); void* place = _aligned_malloc(sizeof(Object), alignof(Object)); (да, порядок противоположный). Или если вам нужен именно malloc, то можно заказать больше памяти для того, чтобы было место для выравнивания, и выровнять указатель при помощи std::align: std::size_t alloc_size = sizeof(Object) + alignof(Object) - 1; void* place = malloc(alloc_size); void* aligned_place = place; if (std::align(alignof(Object), sizeof(Object), aligned_place, alloc_size)) { Object* o = new(aligned_place) Object(); // ... } Эта вторая техника пригодится, если вы захотите выделить в одном буфере несколько объектов.

вторник, 24 декабря 2019 г.

Почему следующий код не приводит к сегфолту?

#c #указатели #malloc


#include 

int main() {
    int i = 0;
    unsigned char (*b)[5];

    b = malloc( 5 );

    for ( ; i < 100 ; i++ ) {

        *(b+i)[0] = 192;
        *(b+i)[1] = 168;
        *(b+i)[2] = 0;
        *(b+i)[3] = 1+i;

        printf( "%d.%d.%d.%d\n", *(b+i)[0], *(b+i)[1], *(b+i)[2], *(b+i)[3] );
    }

    return 0;
}


В моем понимании функция malloc() выделяет только 5 байт , и как минимум на 6-м шаге
цикла ( i == 5 ) указатель должен выйти за рамки отведенной процессу памяти, почему
этого не происходит?

Собираю с помощью gcc version 4.9.2 (Debian 4.9.2-10)
    


Ответы

Ответ 1



Из комментария пользователя @nick_n_a: Потому что размер страницы памяти 4096 байт (по умолчанию, так сказать «квант» памяти), и fail может наступить только если для следущей страницы не сделано allocate. Поэтому даже переходя за грань 4096 fail может быть а может и не быть. Пока программа маленькая такие трюки могут заканчиваться ничем, а при большой программе можно получить глюки, которые очень сложно отловить. Если у вас стоит задача получить seg-fail, то можно пробовать читать/писать с шагом 4096 байт.

Ответ 2



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

Язык C, malloc() и NULL

#c #windows #null #malloc


Хотел бы обсудить, что делать, если malloc() вернул NULL.

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

Например, если пишется загрузчик ресурсов из файла в оперативную память, то лучше
бы сказать "извини, памяти нет", чем внезапно упасть неизвестно по какой причине.

1) От людей, которые пишут под Linux, слышал, что они не проверяют результат вызова
malloc(), обосновывая это тем, что malloc() запросто может вернуть не NULL, а при попытке
обращения к памяти через такой указатель все рухнет. Ибо память изначально выделяется
в виртуальной области, и такая память не связывается с реальной до тех пор, пока к
ней не произойдет обращение.

В Windows 10/8/7/Em/CE тоже так?

2) Если память выделяется маленькими порциями, то может произойти такое, что при
неудачном malloc() мы хотим сделать printf(), не говоря уже про fopen() + fwrite(),
а памяти нет, и все совсем падает. Это реально?

3) Довольно долго думал над тем, как защитить программу, которая крутится на ПЛК,
обеспечивая передачу телеметрии. Сам объект находится далеко - "три для ехать, а потом
еще на вертолете с пересадками". В общем, классический пример промышленных систем.

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

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

Ни разовое аллоцирование, ни "умный" управляющий памятью, - не дают гарантий, а лишь
приводят к сингулярному разрастанию возможных неопределенных поведений.

4) Я так понимаю, на исчерпывающем большинстве промышленных устройств сторожевой
таймер предусмотрен как раз из-за того, что существуют вот такие неразрешимые программным
путем проблемы, как неудачный malloc() и исчерпание памяти?
    


Ответы

Ответ 1



malloc всегда должет возвращать адрес, если malloc вернул null тогда это аварийная ситуация. Вы можете 1) Прекратить работу всей программы. 2) Вы можете прекратить работу "процесса" (не процесса как Process, а класса, библиотеки, функции или набора функций, например процесс компрессии данных), который не может завершить свою работу, освободить память занимаемую этим "процессом". Тут я солидарен с alexolut Надо не забывать что у программы есть детерминированое - определённое поведение, и недетерминированое. Нужно стремится делать детерминированое поведение. В программе желательно до начала процесса (а не вовремя) опеределить сколько нужно на процесс памяти, и не начинать процесс (или работу программы) если его не возможно выполнить. Нужно ограничивать аппетиты пользователя, если вам вместо 100Мб прислали 10Gb, то сообщить что файлы/потоки свыше 2Gb ваша программа не обрабатывает (условно, для примера). Нужно определить заранее на какие обьёмы вы хотите расщитывать, и проверять есть ли они (обьемы) в наличии. Если вы резервируете память столько незнаю сколько - то для небольших программ будет всё ок, а для больших - может закончится нехваткой памяти. Т.е. невозможно сделать malloc столько незнаю cколько. В ответственных за скорость местах лучше использовать пулы памяти. Недостаток пулов - часть "неиспользуемой" памяти просто лежит. Нужно соблюдать несколько правил... и тогда у вас память не закончится неожиданно. резервировать в ответственных местах память заранеее ограничивать глубину рекурсии в рекурсивных функциях (хвостовая рекурсия - отдельный случай) соблюдать порядок malloc и free. Желательно разделять память для долгого использования и для кратковременного. Сколько зарезервировано паралельными процессами. Проверять наличие утечки памяти (когда malloc больше чем free, но для этого нужно знать какое число, разница колличества вызовов malloc/free должна быть, и для этого нужно знать сколько памяти долгого использования зарезервировано). делать пулы, или массивы памяти, для возможности освободить память по завершению "процесса" которую будет использовать конкретный процесс (действие) (упростит освобожнение). память может закончится в двух случаях а) неправильно построеная "модель памяти" (неопределённое поведение программы которое не предусмотрено) б) пользователь использует компьютер с реально маленьким размером оперативки и файлом подкачки. Второй случай редкий, но проверить его на старте стоит. Ещё хочу заметить, что ОС уровня "приложение" оперирует виртуальной памятью, поэтому может такое быть, что ваше приложение занимает меньше памяти чем вы резервировали. Тут Язык C, глобальные массивы часный случай когда реально приложение занимает меньше памяти, чем виртуально. Если для вас важна стабильность - резервируйте при запуске нужное количество памяти - и проблемы с malloc у вас не будет. Ведь главный вопрос не в том хватает памяти или не хватает, а в том - сколько вам нужно.

Ответ 2



Если нужно подготовиться к обработке исчерпания памяти, то первым делом стоит обеспечить (зарезервировать) уже некоторый объём памяти для этих нужд. И всю обработку производить, опираясь на эти, заранее выделенные, объёмы. Когда память закончилась - это значит, что что-то пошло не так, что-то что мы заранее не учли. Ведь в нормальной ситуации устройство должно обычно обеспечивать режим работы 24/7. Поэтому когда память вдруг неожиданно кончилась, самое правильное, что здесь можно сделать, это залогировать проблемную ситуацию и перезапуститься (упомянутый вами сторожевой таймер как раз для этих целей). А вот для логирования как раз будет полезен заранее предопределённый объем память. Работать с ним можно по-разному: либо непосредственно ссылаться на эту память, либо выполнить для неё free и рассчитывать, что последующие malloc, необходимые для корректной обработки аномальной ситуации, отработают корректно, т.е. предоставят недавно освобождённую память.

Ответ 3



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

среда, 18 декабря 2019 г.

Различия между функциями calloc и malloc

#c #функции #память #malloc #calloc


В чем заключается отличие функции malloc от calloc?
Есть ли случаи, когда подходит лишь одна из этих функций?
    


Ответы

Ответ 1



Во-первых, функция calloc, в отличие от malloc, возвращает указатель на инициализированный блок памяти, т.е. изначально содержащий нулевые биты1. Это, однако, совсем не означает, что функция calloc сама занимается обнулением полученного блока памяти в контексте пользовательского процесса. Функция calloc может пользоваться более эффективными средствами обнуления, предоставляемыми подлежащей платформой, т.е., например, выделять память в пуле "обнуленных страниц" ОС или использовать некий механизм "ленивого" обнуления уровня аппаратуры/ОС. То есть если вам нужен обнуленный блок памяти, то в общем случае получение такого блока памяти через calloc может оказаться существенно более эффективной операцией, чем "ручное" обнуление блока памяти, полученного через malloc. Во-вторых, функция calloc, как несложно заметить, сама внутренне занимается вычислением общего размера запрашиваемого блока памяти (буквально - перемножением своих аргументов). При этом функция calloc обязана самостоятельно отследить корректность такого перемножения, т.е. обнаружить ошибку при возникновении переполнения. В случае использования функции malloc такое перемножение (если оно необходимо) выполняет вызывающий код и предотвращение переполнения - обязанность вызывающего кода. 1 С точки зрения языка это не гарантирует правильное формирование нулевых указателей и нулевых значений плавающего типа.

Ответ 2



malloc просто выделяет память, оставляя ее содержимое неопределенным, a calloc ее гарантированно обнуляет. Что не всегда необходимо, зато требует времени...

пятница, 13 декабря 2019 г.

Malloc + free + chdir = segmentation fault?

#linux #c #malloc


Приветствую всех!

Есть такой "код":

#include 
#include 
#include 
#include 
#include 
#include 

/*
 *
 */

// Структура хранящая список содержимого каталога в виде
// массива строк (аля argv). Память для записей выделяется
// из кучи.

typedef struct {
    unsigned int count;
    char **list;
} dirslist_t;


/// Очищает кучу по адресам из принятой структуры.
/// \param dl

void dirs_list_free(dirslist_t *dl) {

    while (dl->count) {
        free(*(dl->list + dl->count));
        dl->count--;

        if (!dl->count) {
            free(*(dl->list)); 
            free(dl->list);
        }

    }
}


/// Возвращает указатель на структуру dirslist_t заполненную новыми данными. 
/// \param dl
/// \param dst_name
/// \return 

dirslist_t *dirs_list(char *dst_name) {

    dirslist_t *dl = malloc(sizeof (dirslist_t));

    // Создаем новые записи. Сразу закинем в начало списка
    // обозначения дочернего и родительского каталогов.
    dl->list = (char**) malloc(sizeof (char*) * 3);

    *(dl->list) = malloc(sizeof (char) * sizeof ("."));
    strcpy(*(dl->list), ".");

    *(dl->list + 1) = malloc(sizeof (char) * sizeof (".."));
    strcpy(*(dl->list + 1), "..");
    // Указваем что в списке уже есть два элемента.
    dl->count = 2;


    // Структура описатель "потока" каталога.
    DIR *DIRp;
    // Структура описатель текущего каталога.
    struct dirent *current_dir;
    // Открываем каталог.
    DIRp = opendir(dst_name);
    // Если вернулся не NULL начинаем обходить записи данного каталога.
    if (DIRp) {
        // Повторяем пока не NULL т.е. пока не закончились записи.
        while ((current_dir = readdir(DIRp)) != NULL) {

            if (!strcmp(current_dir->d_name, "."))
                continue;
            if (!strcmp(current_dir->d_name, ".."))
                continue;

            // Выделяем место под название каталога.
            *(dl->list + dl->count) = malloc(strlen(current_dir->d_name) + 1);
            // Копируем имя в список.
            strcpy(*(dl->list + dl->count), current_dir->d_name);

            // Увеличиваем список.
            dl->count++;
            dl->list = realloc(dl->list, sizeof (char*) * (dl->count + 1));

        }
        //!!!_Если раскомментировать этот printf то упадет раньше_!!!
        //printf("%d\n", (int) dl->count);
        closedir(DIRp);
        return dl;
    } else {
        printf("Return: NULL!\n");
        return NULL;
    }

}
////////////////////////////////////////////////////////////////////////////////

int main(int argc, char **argv) {


    dirslist_t *mdirs;

    for (int k = 0; k < 10; k++) {
        mdirs = dirs_list(".");
        for (int i = 0; i < mdirs->count; i++) {
            printf("%s\n", *(mdirs->list + i));
        }
        dirs_list_free(mdirs);
        free(mdirs);

        chdir("..");
    }

}


Если в функции main закомментировать только chdir("..") то всё работает (выводит
содержимео текущего каталога 10 раз), если же в функции main закомментировать только
dirs_list_free(mdirs) то всё работает как мне надо - показывает список файлов в текущем
каталоге потом, переходит к родительскому и все повторяется, но память естественно
течет, НО, если раскомментированные обе строки то на 2-3 итерации выпадает Segmentation
fault!
В первом случае Valgrind ругается на ошибки, во втором на утечку памяти.
Это лиш кусочек небольшого велосипеда обрезанный до сути. 2й день бьюсь с этой ошибкой.
Перепробовал уже уйму вариантов.
Makefile:

CC=gcc
CFLAGS=-std=c11 -Wall

all:
    $(CC) $(CFLAGS) main.c -o el1
clean:
    rm el1


Заранее Большое Спасибо!
    


Ответы

Ответ 1



Во-первых, хочу сказать, что если у вас есть такой божественный инструмент как Valgrind, то вам очень повезло. Любите его, уважайте его выхлоп и чините то, что он вам говорит. Это ваш лучший друг, сразу после компилятора. В случае вашего кода, Valgrind сразу нашел 2 места, откуда происходит ошибка: место аллокации и место освобождения памяти. А если собрать с -ggdb, то даже номера строк дадут. Вам сразу покажут пальцем, откуда произрастает проблема. Использовать gdb для новичка может быть, наверное, не очень весело, потому новички часто используют отладку принтами. Добавте перед освобождением указателя следующую строчку: fprintf(stderr,"Current element: %d, current pointer %p\n",dl->count, *(dl->list + dl->count)); После чего запустите - вы сразу увидите, что что-то пошло не так... После чего вы можете что-то сделать, чтобы не освобождать последний элемент, память под который была выделена, но сам элемент не аллоцирован. К примеру, в начале функции сделать: dl->count-- И мир заиграет яркими красками счастья и удовольствия. Про другие странности можете почитать другие ответы, но я постарался описать то, как надо отлаживать, чтобы подобных вопросов больше не возникало.

Ответ 2



Первая ошибка, которая бросается в глаза: у вас массив dl->list всегда содержит dl->count + 1 элемент, т.е. на один элемент больше, чем фактически используется. При этом последний (неиспользуемый) элемент dl->list[dl->count] не инициализирован, т.е. содержит мусор. Тем не менее в функции dirs_list_free вы сразу смело делаете free(*(dl->list + dl->count)) т.е. применяете free к "мусорному" указателю. Поведение не определено. Если вам хочется, чтобы в массиве dl->list у вас в конце стоял "запасной" guard element, то пусть он там будет - это, в принципе, неплохая идея, по аналогии со стандартными argc/argv[]. Но хотя бы проинициализируйте его нулевым указателем. Странность: что вот это if (!dl->count) { free(*(dl->list)); free(dl->list); } делает внутри цикла в dirs_list_free? Зачем было засовывать это внутрь цикла и затем проверять if (!dl->count) на каждой итерации, если можно было просто сделать это один раз после цикла? Странность: функция dirs_list выделяет память для самой структуры dirslist_t, а вот функция dirs_list_free эту память не освобождает. Обязанность освобождать саму структуру взвалена на "пользователя". Почему? В честь чего такая ассиметрия? P.S. Зачем вам сдался этот дубовый синтаксис *(dl->list + i)? Почему бы не писать по человечески dl->list[i]?

суббота, 15 июня 2019 г.

Выделение памяти под двумерный массив malloc

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


Ответ

Казалось бы, что может быть прямолинейнее, чем выделение динамически памяти под двумерный массив, а потому должно существовать одно решение.
На самом деле можно по-разному реализовать данную задачу.
Самый простой способ - это когда число колонок в двумерном массиве является константным значением, определенным либо с помощью директивы #define либо с помощью перечислителя. Тогда можно сразу же разместить динамически именно двумерный массив.
Второй подход, это когда число колонок задается во время выполнения программы. В этом случае обычно выделяют несколько одномерных массивов. Сначала выделяют одномерный массив указателей с количеством элементов равных количеству строк в предполагаемом двумерном массиве, А затем для каждого указателя этого одномерного массива выделяют массив объектов базового типа, число элементов которого равно числу колонок в двумерном массиве. В этом варианте важно учитывать, что во время выделения массивов может возникнуть нехватка памяти, а потому желательно правильно удалить те массивы, которые были уже распределены, а иначе программа будет иметь неопределенное состояние.
Третий подход заключается в использовании массивов переменной длины при условии, что C компилятор их поддерживает. Тогда число колонок и строк может быть задано во время выполнения программы, и можно выделить сразу же двумерный массив, а не массив массивов, как было описано в предыдущем абзаце.
Также следует не забывать корректно освобождать выделенную динамически память под массивы, когда массивы уже не нужны.
Ниже приведена демонстрационная программа, в которой показаны перечисленные способы выделения памяти для работы с двумерными массивами или их аналогами.
#include #include #include
#define COLS 10
char ( * first_allocate( size_t rows ) )[COLS] { char ( *s )[COLS] = malloc( rows * sizeof( char[COLS] ) );
return s; }
void first_free( char ( *s )[COLS] ) { free( s ); }
char ** second_allocate( size_t rows, size_t cols ) { char **s = malloc( rows * sizeof( char * ) );
if ( s != NULL ) { size_t i = 0;
while ( i < rows && ( s[i] = malloc( cols * sizeof( char ) ) ) != NULL ) i++;
if ( i != rows ) { for ( size_t j = i; j != 0; j-- ) free( s[j-1] ); free( s ); s = NULL; }
}
return s; }
void second_free( char **s, size_t rows ) { for ( size_t i = 0; i < rows; i++ ) free( s[i] ); free( s ); }
void third_allocate( size_t rows, size_t cols, char ( **s )[rows][cols] ) { *s = malloc( sizeof( char[rows][cols] ) ); }
void third_free( size_t rows, size_t cols, char ( *s )[rows][cols] ) { free( s ); }

int main( void ) { size_t rows = 2; size_t cols = 10;
char ( *s1 )[COLS] = first_allocate( rows ); strcpy( s1[0], "first" ); strcpy( s1[1], "second" );
for ( size_t i = 0; i < rows; i++ ) printf( "%s ", s1[i] ); printf( "
" );
first_free( s1 );
printf( "
" );
char **s2 = second_allocate( rows, cols ); strcpy( s2[0], "first" ); strcpy( s2[1], "second" );
for ( size_t i = 0; i < rows; i++ ) printf( "%s ", s2[i] ); printf( "
" );
second_free( s2, rows );
printf( "
" );
char ( *s3 )[rows][cols] = NULL;
third_allocate( rows, cols, &s3 ); strcpy( ( *s3 )[0], "first" ); strcpy( ( *s3 )[1], "second" );
for ( size_t i = 0; i < rows; i++ ) printf( "%s ", ( *s3 )[i] ); printf( "
" );
third_free( rows, cols, s3 ); }
Вывод программы на консоль будет следующим
first second
first second
first second

понедельник, 10 июня 2019 г.

Выделение памяти без инициализации с помощью оператора new

Возможно ли с помощью оператора new сделать что-то вроде этого:
class MyData { char data[1024]; MyData() {} };
MyData* dataArray = static_cast(malloc(10 * sizeof(MyData)));
Т.е. выделить память под объект или массив объектов без ее инициализации?


Ответ

Конечно. Можно использовать тот же malloc, а можно использовать operator new (или operator new[]), который выделит кусок "сырой" памяти. Отличие operator new от operator new[] - фактически в том, что можно более эффективнее реализовать собственные версии этих операторов. Так что всё, что сказано про operator new, относится и к operator new[]
operator new также запрашивает непрерывный кусок памяти, и возвращает указатель на "нечто" (void*). Никакие конструкторы эта штуковина тоже не вызывает, но уже знает о исключениях и если выделение не удастся, то, кинет исключение, а не вернет nullptr (хотя это не всегда выполняется). На самом деле, сначала еще попытает счастье с new_handler, но опустим этот момент. Также есть версия, которая не кидает исключение, а возвращает nullptr в случае неудачи и версия, которая совсем не выделяет память, а просто параметр-указатель проходит там "транзитом" (такая версия используется для placement new), причем можно еще и перегружать operator new, но сейчас не об этом.
Для освобождения памяти, выделенной с помощью operator new, используется operator delete
Для освобождения памяти, выделенной с помощью operator new[], используется operator delete[]
void *p = operator new(sizeof(T) * count); operator delete (p);
Вызвать конструкторы для инициализации можно с помощью placement new
T *pT = new (p) T[count];
Конечно же, при таком подходе вызов конструкторов и деструкторов целиком на авторе кода.

среда, 3 апреля 2019 г.

Как скопировать массив в динамический массив? (Си)

Есть массив char str[] = "abcdef";
И динамический массив: char *dstr = (char*)malloc(sizeof(char) * N);
Как скопировать str[] в dstr[] ?
P.S. Заранее извиняюсь, если вопрос глупый.


Ответ

Есть 3 способа, нативное копирование области памяти, специальной функцией копирования строк, посимвольный перебор массива и запись в dstr
memcpy(&str, &dstr, strlen(str) + 1); // Копирование области памяти

strcpy(dstr, str); // Специальная функция для строки

// Перебор массива циклом.
int strsize = strlen(str) + 1; for(int i = 0; i < strsize ; i++) { dstr[i] = str[i]; }
Имейте ввиду, в обоих случаях вы должны быть уверены что размер dstr больше на 1 чем длина строки str

среда, 27 февраля 2019 г.

Как динамически создать массив, не зная количества его элементов? [дубликат]

На данный вопрос уже ответили: Как создать динамический массив? 3 ответа В цикле читаю некоторый блок данных по частям различных размеров. Требуется учитывать размеры этих частей - думаю загонять их в массив. Как создать этот массив, если я не знаю сколько частей будет? Возможно ли реализовать такое в языке С стандартными методами?


Ответ

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

четверг, 20 декабря 2018 г.

Явный вызов конструктора для объекта

Как создать объект в динамической памяти используя malloc()? Нужно ведь явно вызывать конструктор, как это сделать?


Ответ

Вам нужен placement new.
Например, так:
void* place = malloc(sizeof(Object)); Object* o = new(place) Object();
Не забудьте в конце вызвать и деструктор вручную:
o->~Object();
и если нужно
free(place);
Документация: https://isocpp.org/wiki/faq/dtors#placement-new

Для случая, когда у вас память выделена заранее, вам придётся позаботиться о выравнивании. Обычный указатель, который возвращает malloc, имеет выравнивание, достаточное для нормально объявленного класса, то есть, класса, определение которого не содержит alignas() (а также определение классов его полей), и значит, ничего больше делать не нужно.
Если это (отсутствие alignas) не гарантировано, у вас есть два пути. Вы можете вместо malloc использовать aligned_alloc (или _aligned_malloc на старых версиях Visual Studio), который выдаёт malloc с нужным выравниванием:
void* place = std::aligned_alloc(alignof(Object), sizeof(Object)); void* place = _aligned_malloc(sizeof(Object), alignof(Object));
(да, порядок противоположный).
Или если вам нужен именно malloc, то можно заказать больше памяти для того, чтобы было место для выравнивания, и выровнять указатель при помощи std::align
std::size_t alloc_size = sizeof(Object) + alignof(Object) - 1; void* place = malloc(alloc_size); void* aligned_place = place; if (std::align(alignof(Object), sizeof(Object), aligned_place, alloc_size)) { Object* o = new(aligned_place) Object(); // ... }
Эта вторая техника пригодится, если вы захотите выделить в одном буфере несколько объектов.

вторник, 27 ноября 2018 г.

Почему следующий код не приводит к сегфолту?

#include
int main() { int i = 0; unsigned char (*b)[5];
b = malloc( 5 );
for ( ; i < 100 ; i++ ) {
*(b+i)[0] = 192; *(b+i)[1] = 168; *(b+i)[2] = 0; *(b+i)[3] = 1+i;
printf( "%d.%d.%d.%d
", *(b+i)[0], *(b+i)[1], *(b+i)[2], *(b+i)[3] ); }
return 0; }
В моем понимании функция malloc() выделяет только 5 байт , и как минимум на 6-м шаге цикла ( i == 5 ) указатель должен выйти за рамки отведенной процессу памяти, почему этого не происходит?
Собираю с помощью gcc version 4.9.2 (Debian 4.9.2-10)


Ответ

Из комментария пользователя @nick_n_a
Потому что размер страницы памяти 4096 байт (по умолчанию, так сказать «квант» памяти), и fail может наступить только если для следущей страницы не сделано allocate. Поэтому даже переходя за грань 4096 fail может быть а может и не быть. Пока программа маленькая такие трюки могут заканчиваться ничем, а при большой программе можно получить глюки, которые очень сложно отловить.
Если у вас стоит задача получить seg-fail, то можно пробовать читать/писать с шагом 4096 байт.

понедельник, 26 ноября 2018 г.

Язык C, malloc() и NULL

Хотел бы обсудить, что делать, если malloc() вернул NULL
Ясное дело, что если ведется разработка библиотеки или какого-либо "кирпичика", который будет использоваться неизвестно в каких условиях, то вызов abort() совершенно неприемлем.
Например, если пишется загрузчик ресурсов из файла в оперативную память, то лучше бы сказать "извини, памяти нет", чем внезапно упасть неизвестно по какой причине.
1) От людей, которые пишут под Linux, слышал, что они не проверяют результат вызова malloc(), обосновывая это тем, что malloc() запросто может вернуть не NULL, а при попытке обращения к памяти через такой указатель все рухнет. Ибо память изначально выделяется в виртуальной области, и такая память не связывается с реальной до тех пор, пока к ней не произойдет обращение.
В Windows 10/8/7/Em/CE тоже так?
2) Если память выделяется маленькими порциями, то может произойти такое, что при неудачном malloc() мы хотим сделать printf(), не говоря уже про fopen() + fwrite(), а памяти нет, и все совсем падает. Это реально?
3) Довольно долго думал над тем, как защитить программу, которая крутится на ПЛК, обеспечивая передачу телеметрии. Сам объект находится далеко - "три для ехать, а потом еще на вертолете с пересадками". В общем, классический пример промышленных систем.
Приложение многопоточное. Одни потоки отправляют/получают данные, другие устанавливают соединение по TCP, третьи - ведут файлы журналов. Много библиотек, часто - закрытых в dll.
В итоге пришел к выводу, что единственное адекватное решение, если malloc() где-то вернул NULL, - это вызов abort(), с последующей скорой перезагрузкой отдельным модулем, который определил, что наше приложение перестало "теребить" какой-то сигнализатор. Защита журналов в этом случае делается тривиальным способом - при каждом старте процесса создается новый журнал. Таким образом, старые журналы могут иметь в конце какой-то недописанный "хвост", но это нестрашно.
Ни разовое аллоцирование, ни "умный" управляющий памятью, - не дают гарантий, а лишь приводят к сингулярному разрастанию возможных неопределенных поведений.
4) Я так понимаю, на исчерпывающем большинстве промышленных устройств сторожевой таймер предусмотрен как раз из-за того, что существуют вот такие неразрешимые программным путем проблемы, как неудачный malloc() и исчерпание памяти?


Ответ

malloc всегда должет возвращать адрес, если malloc вернул null тогда это аварийная ситуация. Вы можете 1) Прекратить работу всей программы. 2) Вы можете прекратить работу "процесса" (не процесса как Process, а класса, библиотеки, функции или набора функций, например процесс компрессии данных), который не может завершить свою работу, освободить память занимаемую этим "процессом". Тут я солидарен с alexolut
Надо не забывать что у программы есть детерминированое - определённое поведение, и недетерминированое. Нужно стремится делать детерминированое поведение. В программе желательно до начала процесса (а не вовремя) опеределить сколько нужно на процесс памяти, и не начинать процесс (или работу программы) если его не возможно выполнить. Нужно ограничивать аппетиты пользователя, если вам вместо 100Мб прислали 10Gb, то сообщить что файлы/потоки свыше 2Gb ваша программа не обрабатывает (условно, для примера). Нужно определить заранее на какие обьёмы вы хотите расщитывать, и проверять есть ли они (обьемы) в наличии. Если вы резервируете память столько незнаю сколько - то для небольших программ будет всё ок, а для больших - может закончится нехваткой памяти. Т.е. невозможно сделать malloc столько незнаю cколько.
В ответственных за скорость местах лучше использовать пулы памяти. Недостаток пулов - часть "неиспользуемой" памяти просто лежит.
Нужно соблюдать несколько правил... и тогда у вас память не закончится неожиданно.
резервировать в ответственных местах память заранеее ограничивать глубину рекурсии в рекурсивных функциях (хвостовая рекурсия - отдельный случай) соблюдать порядок malloc и free. Желательно разделять память для долгого использования и для кратковременного. Сколько зарезервировано паралельными процессами. Проверять наличие утечки памяти (когда malloc больше чем free, но для этого нужно знать какое число, разница колличества вызовов malloc/free должна быть, и для этого нужно знать сколько памяти долгого использования зарезервировано). делать пулы, или массивы памяти, для возможности освободить память по завершению "процесса" которую будет использовать конкретный процесс (действие) (упростит освобожнение). память может закончится в двух случаях а) неправильно построеная "модель памяти" (неопределённое поведение программы которое не предусмотрено) б) пользователь использует компьютер с реально маленьким размером оперативки и файлом подкачки. Второй случай редкий, но проверить его на старте стоит.
Ещё хочу заметить, что ОС уровня "приложение" оперирует виртуальной памятью, поэтому может такое быть, что ваше приложение занимает меньше памяти чем вы резервировали. Тут Язык C, глобальные массивы часный случай когда реально приложение занимает меньше памяти, чем виртуально.
Если для вас важна стабильность - резервируйте при запуске нужное количество памяти - и проблемы с malloc у вас не будет. Ведь главный вопрос не в том хватает памяти или не хватает, а в том - сколько вам нужно.