LINUX.ORG.RU

Очистка памяти после создания двумерного «непрерывного в памяти» массива

 , , , ,


2

2

«Я познаю мир», это главный тэг этой записи.

Допустим, хочется в C99 (в С++ такой способ тоже сработает) хочется создать двумерный массив в «непрерывной!» области памяти, чтобы в дальнейшем обращаться к его элементам как a[i][j]:

#include <stdio.h>
#include <stdlib.h>

int main()
{
int n = 12;

double (*a)[n] = (double (*)[n])malloc(n * n *  sizeof(double))

a[11][0] = 40.0;
printf("a[11][0] = %4.2f\n", a[11][0]);
// выход за границу массива с заползанием в следующий блок:
a[10][12] = 50.0;
printf("a[11][0] = %4.2f\n", a[11][0]);
printf("a[10][12] = %4.2f\n", a[10][12]);

return 0;
}

Подскажите, кто-нибудь, пожалуйста, как при помощи free() очистить память выделенного таким образом массива?

P.S. Казалось бы, что при создании подобного массива можно было бы описать его так:

#include <stdio.h>
#include <stdlib.h>

int main()
{
int n = 12;

// при присваивании, не выходя за границу массива, можно перезаписывать другие элементы:
double (*b)[1] = (double (*)[1])malloc (n * n * sizeof(double));
b[11][0] = 40.0;
printf("\nb[11][0] = %4.2f\n", b[11][0]);
// перезаписывает значение b[11][0]:
b[5][6] = 50.0;
printf("b[11][0] = %4.2f\n", b[11][0]);
printf("b[5][6] = %4.2f\n", b[5][6]);

return 0;
}

★★★★★

Последнее исправление: grem (всего исправлений: 2)

что это была за херня такая? ты что написал вообще.

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

ckotinko ☆☆☆
()

Создай одномерный массив и с помощью адресной арифметики работай с ним как с двумерным

kasha
()

как при помощи free() очистить память выделенного таким образом массива?

Ничего необычного:

free(a);

в С++ такой способ тоже сработает

На самом деле это расширение в C++.

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 1)
Ответ на: комментарий от kasha

Создай одномерный массив и с помощью адресной арифметики работай с ним как с двумерным

«Этим нас не удивишь!». Я ж сразу написал, что хотеть обращение к элементу как a[i][j], а не a[n*i+j], но чтобы в памяти массив (в данном случае он тоже линейный, а иначе и быть не может), лежал сплошным куском. При выделении в цикле его разбросает в куче как получится, разве не так?

grem ★★★★★
() автор топика

Наркомания. Последние стандарты C оставляют после себя стойкое ощущение, что пишут их какие-то упоротые.

Gvidon ★★★★
()
Ответ на: комментарий от xaizek

Хм, осталось вспомнить, чего он у меня крашился раньше в дебагере при таком вызове :( Сейчас не крашится, но после такой «очистки» элементу массива всё равно можно присвоить новое значение и вывести его. B программа даже не падает.

Угу, в С++ через расширение, так как malloc не его родная функция.

а вот для одномерного при такой очистке падает:

double *cc = (double*) malloc(sizeof(double));

cc[0] = 2.0;
printf("cc = %4.2f\n", cc[0]);
free(cc);
cc[0]=3333.0;
printf("cc = %4.2f\n", cc[0]);

grem ★★★★★
() автор топика
Последнее исправление: grem (всего исправлений: 2)
Ответ на: комментарий от grem

после такой «очистки» элементу массива всё равно можно присвоить новое значение и вывести его.

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

anonymous
()
Ответ на: комментарий от grem

а вот для одномерного при такой очистке падает:

ну, он же падает не на free(), а на следующей строке

anonymous
()
Ответ на: комментарий от grem

Угу, в С++ через расширение, так как malloc не его родная функция.

Родная. Но VLA в C++ нет.

а вот для одномерного при такой очистке падает

free() это освобождение, а не очистка. Память после free() нельзя использовать.

xaizek ★★★★★
()
Ответ на: комментарий от anonymous

да, т.к. уже, видимо, освободил память и теперь там пытаюсь присвоить.

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

странно, fflush(stdout), не исправляет ситуации и в первом случае с массивом a всё равно выводит новые значения после очистки. Либо это не та функции для очистки кэша.

grem ★★★★★
() автор топика
Ответ на: комментарий от xaizek

как заставить программу 'падать' после присвоения нового значения для массива 'a' если уже было использовано free()?

grem ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Можно, что-нибудь, как мне подсказали, содержащее в виде

 double & arr(i,j){ return arr=i*n+j;}

или просто прописать #define arr(i,j) arr=i*n+j

grem ★★★★★
() автор топика
Ответ на: комментарий от grem

странно, fflush(stdout), не исправляет ситуации

гы-гы, сделал мой день! :)
у потоков это называется буферизация, а не кеш, впрочем, ты и освобождение очисткой называешь, короче, тебе надо K&R прочитать внимательно раза три-четыре

anonymous
()
Ответ на: комментарий от anonymous

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

Для примера отсюда (Очистка памяти после создания двумерного «непрерывного в памяти» массива (комментарий)) приложение падает, как и ожидается. В исходном примере продолжает себе работать дальше с присвоением элементов, их суммированием и выводом на экран, словно free(a) и не вызывали.

grem ★★★★★
() автор топика
Ответ на: комментарий от grem

В исходном примере продолжает себе работать дальше с присвоением элементов, их суммированием и выводом на экран, словно free(a) и не вызывали.

не знаю, что такое «исходный пример», если речь о стартовом сообщении топика, то там два примера, и ни в одном нет ни суммирования, ни вызова free(), хотя присвоения некоторые есть, да...

anonymous
()
Ответ на: комментарий от grem

Можно во время разработки/тестов использовать санитайзер и настроить на немедленное падение. Или запускать под valgrind. C и C++ сами по себе такое не делают, надо следить самому.

xaizek ★★★★★
()
Ответ на: комментарий от anonymous

имеется ввиду, что если туда добавить

free(a);

a[11][0]  = 500000.0;
printf("a[11][0] = %4.2f\n", a[11][0]);
a[0][11]  = a[11][0] + 1100000.0;
printf("a[0][11] = %4.2f\n", a[0][11]);

то всё спокойно отрабатывает и выводит новые значения на экран. Можно, конечно, сразу после вызова free(a) добавить на всякий случай «a = NULL», тогда да, будет падать при попытке записать в что-нибуль в элемент массива. Почему-то фраза

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

попалась пока только в википедии о_О

grem ★★★★★
() автор топика
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от grem

Зачем ты постоянно пытаешься выстрелить себе в ногу ? Используй адресную арифметику, calloc(), valgrind, ну и по желанию

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

И будет у тебя все хорошо. А то что делаешь ты называется use-after-free, и влечет за собой кучу проблем и багов.

joy4eg ★★★★★
()
Последнее исправление: joy4eg (всего исправлений: 1)
Ответ на: комментарий от xaizek

Память после free() нельзя использовать.

Кажется, я начал что-то подозревать :). Free() указывает, что блок памяти свободен и туда теперь можно что-то записывать. Но при этом указатель «a» всё ещё продолжает указывать на эту же область памяти и туда, гипотетически, при определённых условиях, можно успеть записать новые значения и использовать их до обнуления указатели или до того, как этот участок памяти займёт кто-то другой?

grem ★★★★★
() автор топика
Ответ на: комментарий от joy4eg

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

И будет у тебя все хорошо.

С таким «хорошо» лучше на яве программить и не лезть в C.

vodz ★★★★★
()
Ответ на: комментарий от grem

Угу. Указатель сам по себе не обнулится. Он продолжает указывать туда же, но соответствующий блок памяти уже помечен как свободный. Проблема в том, что свободные блоки могут содержать служебную информацию о куче, которую можно разрушить выполнив запись. Так что после free() блока о нём надо забыть и не использовать.

xaizek ★★★★★
()
Ответ на: комментарий от joy4eg

И будет у тебя все хорошо. А то что делаешь ты называется use-after-free, и влечет за собой кучу проблем и багов.

Угу, поэтому меня это и насторожило. Наверное, из-за такого последующего присваивания элементам массива я и решил, что «free(a)» не совсем то, что мне было нужно для освобождения памяти занятой указателем. Немного смутило, что использование «free(*a)» тоже срабатывает (всё равно это одно и то же), поэтому при использовании «free(a)» у меня не было уверенности, что я вызвал внутри неё именно тот указатель и вся выделенная память действительно освободилась.

grem ★★★★★
() автор топика
Ответ на: комментарий от grem

то всё спокойно отрабатывает и выводит новые значения на экран.

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

не пойму, что ты хочешь то?

anonymous
()
Ответ на: комментарий от anonymous

Уже всё более менее («не менее чем при создании темы») понятно стало, что нужно в обязательном порядке указатель обнулять. Но в том же K&R, Кочане, Прате, Дейтеле, при беглом осмотре, максимум что пишут, это проверять не равен ли указатель NULL после попытки выделить память, а про желательное обнуление указателей, во избежание случайного (или из-за опечатки) запихивания значений в свободную память ничего не попалось.

grem ★★★★★
() автор топика
Ответ на: комментарий от grem

Немного смутило, что использование «free(*a)» тоже срабатывает (всё равно это одно и то же)

всё равно это одно и то же

НЕТ! Это совершенно разные вещи!

joy4eg ★★★★★
()
Ответ на: комментарий от xaizek

Так что после free() блока о нём надо забыть и не использовать.

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

P.S. Всем спасибо.

grem ★★★★★
() автор топика
Ответ на: комментарий от grem

во избежание случайного (или из-за опечатки) запихивания значений в свободную память ничего не попалось.

Потому что это бред. Присваивание 0 самому указателю это защита от prt[0...page_size/sizeof(*ptr)] и нисколько вас не защитит от скрытой ошибки присваивания в ptr[1000]. Не нужно оно вам - так и забудьте о этом указателе.

vodz ★★★★★
()
Ответ на: комментарий от joy4eg

НЕТ! Это совершенно разные вещи!

В дебаггере у них отображался один и тот же адрес. Мне нужно больше спать. Что из этого тогда дейтсвительно полностью освобождает память на выделенный double (*a)[n] = (double (*)[n])malloc(n * sizeof(double));?

grem ★★★★★
() автор топика
Ответ на: комментарий от vodz

Спасибо. Обдумаю это получше, когда чуть лучше высплюсь, уже ничего не соображаю.

grem ★★★★★
() автор топика
Ответ на: комментарий от grem

Для поиска таких ситуация и используют тесты с санитайзерами и valgrind.

xaizek ★★★★★
()
Ответ на: комментарий от grem

Самое простое:

free(a); 
a = nullptr; 
a[0] = 1; //сегфолт

Ограничено, и, конечно, не работает если
b = a; 
free(a); 
a = nullptr;  
b[0] = 1;

ymuv ★★★★
()
Ответ на: комментарий от grem

Сделай

a = NULL;
после free() и тогда будет падать. P.S. Оратор выше меня опередил.

WRG ★★★★
()
Последнее исправление: WRG (всего исправлений: 1)
Ответ на: комментарий от tyro33
#define arr(i,j) i*n+j

Плохо потому что макрос это не функция. Вот смотри:

мы передаём в arr i+5 и j

тогда макрос раскроется в

i + 5*n +j 
а не в
(i + 5)*n + j

Так что лучше использовать

#define arr(i,j) (i)*n+(j)

А ещё лучше использовать функцию, так как здесь у нас один один тип(size_t), и нам не нужна перегрузка. Макросы в виде функций нужно использовать только когда нужна перегрузка функций, а компилятор не поддерживает C11

В C11 используется generic макросы.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Да, про скобки я забыл здесь. Подумал, что имелось ввиду вообще лучше не использовать макросы. Функцию, чтобы было a[Indx(i, j)]?

grem ★★★★★
() автор топика
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от grem

Да, функцию описывать чуть чуть дольше, зато будет понадёжнее макроса будет. (Можно конечно и макрос такой же написать, который будет приводить типы, и делать ассерты, но обычно такие длинные макросы никто не пишет) Можно assert подсунуть, опять же будет приведение типов до size_t(а то, если int i,j,n; и индекс больше 2^31, так как макрос не приведёт типы, то произойдёт overflow)

size_t Indx(size_t i, size_t j)
{
    assert (0 < n); // число элементов в строке должно быть больше нуля
    if (0 != i)
    {
        assert ( SIZE_MAX / i >= n); // проверяем что нет переполнения при i*n
    }
    assert (SIZE_MAX - i * n >= j); // проверяем что нет переполнения при i*n + j
    return i * n + j;
}

Имхо макросы использовать можно в C коде когда нужна именно перегруженная функция, и относительно простой логикой.

fsb4000 ★★★★★
()
Ответ на: комментарий от invy

спасибо, clang «на-посмотреть» вчера скачал вместе с html-документацией, выложенной на сайте

grem ★★★★★
() автор топика
Ответ на: комментарий от fsb4000

Имхо макросы использовать можно в C коде когда нужна именно перегруженная функция, и относительно простой логикой.

Для вашего примера вполне может быть n через #define.

vodz ★★★★★
()
Ответ на: комментарий от joy4eg

Все приходит с опытом ;)

Я розовая звезда дятел! :)
double (*a)[n] = (double (*)[n])malloc(n * sizeof(double));, хоть мы и объявили тип double (*a)[n] для 12 элементов, он, естественно, выделяет память только под 12 элементов, а не для 144. Поэтому a[11][0] = 40.0;, который до free(a), пишет не в выделенный участок памяти, а за его пределы, повреждая кучу. Инициализация должна быть следующей (подправлю в теме):
double (*a)[n] = (double (*)[n])malloc(n * n * sizeof(double));

grem ★★★★★
() автор топика
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от kawaii_neko

Возьми python + numpy. Возьми python + numpy.

я ничего и не считаю, уже и поизвращаться нельзя

grem ★★★★★
() автор топика
std::vector<T> arr(rows * cols);
arr[y * cols + x] = 42;

Ваш кэп

yoghurt ★★★★★
()
Ответ на: комментарий от grem

Я ж сразу написал, что хотеть обращение к элементу как a[j], а не a[n*i+j]

Сахарок сахарный. Если такого действительно хочется, да ещё чтоб с выравниванием строк/элементов (мало ли), то только костылять свой класс над плоским буфером и переопределять операторы.

yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

чтобы в дальнейшем обращаться к его элементам как a[i][j]

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

свой класс

и как этот «класс» будет выглядеть в чистом С? Функцию или макрос описывали выше.

На самом деле было просто интересно как сделеть подобную вещь, С позволяет внутренними средствами. Проблема была ещё и в том, что я сначала неправильно выделял память, из-за чего при присвоении значений некоторым элементам массива повреждал кучу. Меня переглючило, что раз описание типа уже говорит, что там n элементов, то и выделить он для каждого из них должен, забыв, что перед malloc просто преобразование типа из (void *) в (double (*)[n]), которое в чистом C можно и не писать, в крайнем случае warning будет и всё.

grem ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.