LINUX.ORG.RU

вопрос по C (массив указателей на указатели)


0

1

в общем есть вопрос. есть программа, в программе есть случайное количество char[] произвольной длины, количество от 10, до 1000. в общем вопрос.

как выделить память с помощью malloc(number_ofstrings * 4);

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

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

надеюсь, понятно спрашиваю

Перемещено mono из admin

★★★
Ответ на: А вот тут я весь в свисток уйду... от anonymous

Мы им воспользоваться уже не сможем гарантированно.

приктика «зануления» указателей работает только тогда, когда ты либо ассертами свои же ошибки ловишь в дебаг версии, или не дай бог проверяешь каждый указатель на ноль даже в релизе и тыкаешь юзеру, что он дурак^W что бы он обратился к разработчику.

а так, разницы в этом

char a = ((char *)(0x12345678))[0];
и этом
char a = ((char *)(NULL))[0];
нет никакой. и там, и тут получишь сегфолт.

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

При групповух...

... эээ... при групповой разработке или в разделяемой библиотеке прямо в релизе. Накладных расходов на присваивание NULL не так много, но защита от дурака хорошая.

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

anonymous
()
Ответ на: При групповух... от anonymous

Да, согласен. В теории между теорией и практикой разницы быть не должно, но практика говорит об обратном.

Если говорить о спп то, лично я предпочитают raii и shared_ptrs. Со временем начинают надоедать эти самые занулёные или нет указатели.

nanoolinux ★★★★
()
Ответ на: комментарий от i-rinat

Тут дело не в use after free, а в

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

Сфигали присваивание нуля какому-то указателю чего-то там принудительно скажет «менеджеру памяти»? :) Если б так можно было делать free/delete вообще не нужны были бы, была бы автоматическая сборка мусора.

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

чего-то там принудительно скажет «менеджеру памяти»

А, я это и не заметил. Но зануление — здравая идея.

Если б так можно было делать free/delete вообще не нужны были бы, была бы автоматическая сборка мусора.

Ну так это, http://www.hpl.hp.com/personal/Hans_Boehm/gc/. Говорят, хорошая штука.

i-rinat ★★★★★
()
Ответ на: комментарий от nanoolinux

Я немного проще делаю...

... В коде «для себя» я просто пишу:

#define freez(ptr) if(ptr) {free(ptr); ptr=NULL;}

В коде «на сторону» вместо freez, было бы FREEZ, т.к. это макрос, а их имена принято выделять заглавными.

Собственно, Ваш код по освобождению памяти был бы очевидно переписан — просто меняем free на freez. Учитывая то, что мы пользуемся макросом, он в любом случае при компиляции (препроцессор сработает) будет подставлен в тело кода.

В «крестах» да, есть raii, но у меня в основном С. «Кресты» в работе есть, но не много.

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

На Вашем месте я бы что-нибудь...

... погуглил про dlmalloc например. Как именно Doug Lea реализовал «управление памятью» («менеджер» от англ. «to manage» — «управлять»). С этим стоит познакомится. Ну и стоит познакомиться с управлением памятью в glibc в общем и целом. Причина здесь проста — С-приложение само сообщает системе что некий участок памяти ему больше не нужен. В рантайме. Как именно — гуглите, я описал «на пальцах».

GC («сборщик мусора») в С на хер ни кому не был нужен и до сей поры не нужен и далее не понадобится. Ибо С-программист в отличие от этих ваших пыхеров и знатоков языка Ребе и прочей скриптовой погребени в курсе как (и, самое главное, когда) ему нужно освободить указатель или иную область памяти. Он свободен от состязания с GC в идиотизме. ;) Потому как GC в случае работы С-программы должен как-то там отслеживать необходимость в тех или иных областях памяти, определять их необходимость для программы и, по возможности, зачищать, возвращая операционной системе для использования. Это резко уронит производительность, а С это быстрый язык. По этой причине — хочется GC? Прямиком в пых, яву или куда угодно. Но только вон из С. Не ваше и не для вас. Не всем дано, я понимаю.

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

А! GC от Ганса Боема...

... Помнится, я тоже сперва подумал что это панацея. В «крестах» (где производительность не столь важна), да... Иногда работает. В С... нет... лучше не нужно. Лучше контролировать все самостоятельно. В итоге отказался. Но это мое такое сугубое IMHO. Может и поможет кому... :)))

anonymous
()
Ответ на: На Вашем месте я бы что-нибудь... от anonymous

С-приложение само сообщает системе что некий участок памяти ему больше не нужен. В рантайме. Как именно — гуглите, я описал «на пальцах».

Да, С-приложение вызывает free(), чтобы «сообщить системе что некий участок памяти ему больше не нужен». Не знаю уж, что тут гуглить. И, ясный пень, в рантайме, не в компайл-тайме же. А присваивание указателю нуля с целью чего-то там дополнительно рантайму сообщить может работать только если кто-то где-то все указатели отслеживает. В С это невозможно, поэтому на вашем месте я бы каких-нибудь таблеток принял, выдохнул и перестал говорить загадками :)

roof ★★
()
Ответ на: На Вашем месте я бы что-нибудь... от anonymous

Не всем дано, я понимаю.

Ох уж эти гики. :) Не всем дано музыку писать как Бах. Не всем дано раскладывать мир по полочкам, как Ньютон. А код на сях пописывать может кто угодно, нечем тут гордиться.

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

Нет...

... Не правильно. Ни кто ни где не отслеживает указатели. Их отслеживает сам программист. О чём, собственно говоря, и речь.

Пример кода:

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

int main(int argc, char *argv[]){
	char *vuln_ptr;

	vuln_ptr = calloc(strlen(argv[1]), sizeof(char));
	strncpy(vuln_ptr, argv[1], strlen(argv[1]));
	printf("Test 1: %s\n", vuln_ptr);
	free(vuln_ptr);
	strncpy(vuln_ptr, argv[1], strlen(argv[1]));
	printf("Test 2: %s, free() was done, so what?\n", vuln_ptr);
	vuln_ptr = NULL;
	strncpy(vuln_ptr, argv[1], strlen(argv[1]));
	printf("Test 3: %s\n", vuln_ptr);
	return 0;
}

Внимание — вопрос. Почему после free(vuln_ptr); я всё-таки заряжаю в vuln_ptr строку? И самая загадка — что произойдёт после vuln_ptr = NULL?

Что Вам погуглить? Ну, в классическом случае Вам уже сказали выше — google://usage+after+free. Погуглите. А, в данном конкретном случае, я бы ещё google://null+pointer+dereferencing dыполнил бы, т.к. применительно к указателям в коде выше это оно самое и есть. :)))

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

Ну да...

... «пописать» можно где угодно и кому угодно. Только вот в «новом цифровом мире», где всё завязано на «портируемый ассемблер», т.е., С, было бы глупо писать абы как. Я даже спорить не буду с тем, что большая часть софта, используемая Вами ежедневно это либо С, либо С++. ;)

В С радует то, что не все выдерживают собственную глупость и, как правило, сваливают от С куда-нибудь. ;)

anonymous
()
Ответ на: Ну да... от anonymous

судя по тому, что дискуссия разгорелась ярким пламенем, тут пока что выдерживают)))))

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

Да хрень это какая-то, а не дискуссия. Нашли о чём спорить. Занулять/не занулять, gc нужен/не нужен. Фигня какая-то, читать стыдно.

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

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

IvanR ★★★
() автор топика
Ответ на: Нет... от anonymous

Отвечаю по пунктам:

  1. После free память остается доступной для записи и у нас есть ее адрес. Поэтому можно беспрепятственно в нее писать и читать из нее. Ничего сакрального тут нет. Ясный пень, эта же память после освобождения может быть выделена под другие данные и мы их повредим. Это ответственность программиста.
  2. После vuln_ptr = NULL по адресу, к которому привязано имя vuln_ptr запишется значение, не являющееся валидным адресом, старое его значение (адрес выделенного и затем освобожденного участка памяти) будет потеряно и мы не сможем им воспользоваться. При попытке разыменовать этот указатель неизбежно произойдет косяк, для этого его и зануляют, чтоб этот косяк сразу обнаружить, а не заниматься неделю отладкой кода, который не падает, но и не работает, как ожидалось. Или работает, но не всегда.
  3. и про usage after free и про null+pointer+dereferencing я и без гугла знаю. Расскажите лучше, что погуглить, чтобы найти, откуда вы взяли эту феерическую ересь, что зануление указателя что-то сообщает системе управления памятью?
roof ★★
()
Ответ на: комментарий от nanoolinux

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

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

я вот не пойму, для чего тру C программисту нужны вызовы malloc и free, в пределах данного процесса все равно никто, кроме программиста писать в память не сможет, ниодин другой процесс в «эту» память писать тоже не сможет, так зачем???? можно же все самому контрлировать????

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

Например, в винде есть VitrualAllocEx и WriteProcessMemory, позволяющие писать в память другого процесса =) И кроме тебя твою память использует еще библиотечный код, написанный не тобой. Надеюсь, в определение «тру» не входит необходимость обходиться без сторонних библиотек? :)

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

Нууу... Милейший...

... Именно полная (процентов на 90) ерунда и есть. Пожалуй, я сформулирую свои комментарии по Вашим же пунктам. В принципе, коль скоро Вы взяли на себя труд выписать здесь аж 3 пункта, то Вам наверное следовало бы писать по делу (если есть что сказать), а не всякую фигню «общего назначения». Я расскажу что именно следовало Вам написать уж коль скоро Вы тут позиционируете себя как «хакира офигенного» и что Вы не написали, к сожалению. Причина здесь на мой взгляд проста — у Вас ЧСВ малость поопухло, да баттхёрт глаза застит. :))) Ща полечим, милейший... :)))

1. Да, Капитан, это очевидно. Мы можем читать и писать в некую область памяти после её освобождения по free(). Странно, что Вы не написали почему мы можем это делать. Оk, я заполню пробел.

То, что после free() память остаётся доступной Вам ни о чём не говорит? Ну, например, о том, что free() освобождает память по некоему адресу, но эта функция ровным счётом ни чего не делает с переменной, содержащей адрес данного (освобождаемого) участка памяти. К коду, сударь из вопрос по C (массив указателей на указатели) (комментарий). Смотрим на ltrace ./a.out test:

__libc_start_main(0x80484b4, 2, 0xbf8048b4, 0x8048620 <unfinished ...>
calloc(4, 1)                                                                 = 0x844d008
strncpy(0x844d008, "test", 4)                                                = 0x844d008
printf("Test 1: %s\n", "test"Test 1: test
)                                               = 13
free(0x844d008)                                                              = <void>
strncpy(0x844d008, "test", 4)                                                = 0x844d008
printf("Test 2: %s, free() was done, so "..., "test"Test 2: test, free() was done, so what?
)                        = 40
strncpy(0, "test", 4 <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

Несложно обратить внимание на используемый адрес? Вы его видите? Надеюсь, что да. Продолжаем, если этот факт подтверждает мой тезис о том, что free() очищает область памяти, на которую указывает указатель, но не сам указатель. Причины такого поведения free() очевидны? free() не определяло сhar *vuln_ptr и делать с данным vuln_ptr что-либо не имеет права. Здесь ставим точку по данному пункту.

2. То, что мы присваиваем NULL некоему указателю (надеюсь, напоминать что NULL это 0 или 0х0 не нужно?) приводит к следующему. Если мы указали NULL, то процесс попытается обратиться к странице виртуальной памяти с адресом 0х00. Этот адрес вполне может быть в виртуальном адресном пространстве процесса, но ядро защищает от доступа к нему. При попытке обращения генерируется сигнал SIGSEGV для данного процесса и он умирает.

Таким образом, запись невалидного адреса превращается из бессмыслицы (какой адрес считать валидным, а какой нет?) во вполне осмысленную вещь. Ядро пришибает процесс не потому что там записан какой-то неясный адрес (а вдруг там валидный адрес? С Ваших слов выходит что там может оказаться любой адрес), а именно ноль. Этот момент так же критически важен для рассмотрения поведения программы — в С недопустимы неясности вида «какой-то невалидный адрес». В С всё вполне логично организовано. То, что Вы не видите в чём-то логики, не означает того, что там её нет. Видимо, это просто Вы матчасть слабовато знаете. По данному пункту так же точка.

3. Ни как не могу понять в чём у Вас проблема с тем, что погуглить — выше я Вам сказал что именно информация о том, как реализовано распределение памяти в Linux (про dlmalloc от Дуга Леа) Вам может помочь. Почему-то Вы не стали гуглить по данной теме. Жаль. Вас бы малость поотпустило... :)))

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

А тут всё просто...

... почти просто, точнее.

Формально говоря, поведение malloc/calloc/realloc/free и иже с ними определено тем, о чём я говорил нашему... самоуважаемому коллеге.

Проблема в том, что в Linux процесс (поток, процесс... на данный момент не столь суть важно) живёт в «кооперативной» среде. Т.е., память в любой момент может быть выделена/освобождена и, собственно говоря, сложность здесь в том, что это процесс крайне непредсказуемый. Например, какой-то программе потребовалось 4Gb памяти. В системе 2 Gb RAM и 4Gb свопа, итого, 6Gb. Система поскрипит винтом, но 4 Gb выделит.

/* Иногда с мест раздаются призывы не делать своп. Смело забейте на них — своп для таких случаев и нужен. Он помогает выжить системе и не упасть окончательно. Однако, не стоит такую практику из разряда от случая к случаю переводить в постоянный подход и постоянно уповать на своп. */

Итак. 4Gb у Вас есть. Есть они по причине того, что в Linux реализована «виртуальная память». Т.е., если по-просту, то вся память это RAM+swap. Для управления распределением памяти есть механизм, но о нём ниже. Пока я вынужден заметить что на самом деле, Вашей программе ни чего не принадлежит. Ей принадлежит только указатель на область памяти, которая может быть выделена, а может и нет. Указатель это просто переменная в сегменте bss (неинициализированных данных) и всё. Реальное выделение памяти в куче производится именно в рантайме ф-ей malloc(). Другой расклад если Вы принудительно зарезервируете эти 4Gb. Т.е., объявите массив такого размера в коде, в сегменте data.

В случае, если область памяти не выделена, всё просто. Значит, у системы для Вашей программы нет памяти. Если память выделена (malloc() вернула указатель на область), то содержимое памяти будет неинициализировано. Формально говоря, там будут ошмётки данных других программ и полный треш. По этой причине делается memset() после malloc() или просто calloc(). Эти области памяти берутся из общего пула системной (уже виртуальной) памяти, т.к., повторяю, изначально в адресном пр-ве Вашего процесса их нет. Если бы они там изначально были, то malloc() всегда возвращала бы указатель на участок памяти и ни когда не возвращала бы NULL.

anonymous
()
Ответ на: А тут всё просто... от anonymous

Однако, если Вы посмотрите на strace ./a.out test, то там не увидите malloc/memset, там будут brk/mmap/mmap2... Ни чего странного. На деле, malloc (и её собратья) просто стандартизирована. Реализация (вот мы и добрались) в Linux создана Doug Lea. В других системах другие механизмы, в частности в соляре SysV (двоичные деревья), но это не мешает после перекомпиляции запустить в соляре программу из Linux и наоборот.

Мех-м им. Дуга Леа предусматривает что вся куча Вашей программы организована в виде «сhunks» (набора блоков). Грубо говоря, в начале там есть размер предыдущего блока (если не был распределён), либо данные (чтобы не терять даром 4 байта расределённого блока). Далее там же хранится размер выделенного по запросу Вашей программы блока и, собственно, данные («data»), содержащиеся в блоке. С областью «data» Ваша программа и работает посредством memset/memove/... На эту же область malloc возвращает указатель (опять-таки, заметьте — в рантайме).

При освобождении памяти по free(), блоки памяти приходится реорганизовать. Вначале dlmalloc проверит свободны ли соседние блоки, чтобы слить освобождаемый блок с другими свободными и уменьшить фрагментацию памяти. Такая проверка производится при каждом вызове free() внутренней ф-ей chunk_free() и для этого первые 8 байт блока содержат при освобождении блока указатели на предыдущий и последующий блоки. Причём, я подчеркну ещё раз -- эта работа производится в рантайме. Освобождённые блоки передаются ОС для последующего использования — ещё раз заметьте — Вашей программе на самом деле принадлежит только указатель на участок памяти и не более, пока блок не выделен, когда выделен, то ядро будет защищать блок пока он Вашей программе нужен (man mmap/mmap2/munmap). Но на самом деле, участок освобождаемой памяти просто помечается как неиспользуемый и данные в нём не уничтожаются. dlmalloc и без того есть чем заняться, т.к. память это один из самых дорогих ресурсов в системе. И лучше бы её освобождать ASAP, т.к. она может понадобиться другим процессам (с этого мы и начали).

Для того, чтобы из соседних процессов в блоки, помеченные как использованные, не писали бы лишнего, ядро обеспечивает защиту использованных в данный момент блоков. И прорваться в адресное пространство другого процесса задача не всегда тривиальная. Но зато можно довольно свободно прочесть что там было раньше например. Единственное, в чём может помочь знание того, как там устроены управляющие блоки dlmalloc — в проведении атаки, но это мы тут рассматривать не будем. ;)

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

Тред для себя закрыл, если не будет толковых вопросов.

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

Хорошо...

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

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

И когда производится очистка памяти? И обнуление указателя? Неужто в компайл-тайме? :) Я удивлён... :)))

Нет? Ну а к чему претензии-то тогда? Что вызвало Ваш баттхёрт? :))) В упор не понимаю...

anonymous
()
Ответ на: Нууу... Милейший... от anonymous

Охохохохошеньки... И этот человек пишет мне про ЧСВ и «крутых хакиров». Я - обычный человек, больше сисадмин, чем программист, и то мне смешно читать ваши глупости. Итак, из чего состоит ваша простыня:

  1. Троллинг и провокации (первый абзац). Даже отвечать не буду, вы уже достаточно опозорились перед любым умным человеком.
  2. Высеры, достойные Капитана Очевидность,оформленные в псевдоакадемическом стиле. (Пункты 1 и 2). Наводят на мысль о том, что вы не программист, а преподаватель провинциального техникума, привыкший самоутверждаться за счет тупых студентов, или, что более вероятно, студент, подражающий такому преподавателю. Пункт 1 так же показывает, что вы не различаете область памяти и указатель на нее, что только утверждает меня в этой мысли.
  3. Немного разумных мыслей в пункте 2, но относящихся не ко всем системам. NULL-то он, конечно ноль, но NULL-адреса это обычно диапазон, величина и расположение которого зависит от системы. Ну да ок, преподавателю ПТУ таких вещей можно и не знать.
  4. Проблемы погуглить нет, но гуглинг не дал ответа на мой вопрос.

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

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

Угу... Конечччно...

... В п.1 сказано буквально то, о чём Вы изволили «умолчать» — почему мы можем читать и писать по указанному адресу. Это написано «своими словами» (моими, но, к сожалению не Вашими). И написано чтобы не цитировать http://opengroup.org/onlinepubs/007908775/xsh/free.html, где сказано:

The free() function causes the space pointed to by ptr to be deallocated; that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by the calloc(), malloc(), realloc() or valloc() function, or if the space is deallocated by a call to free() or realloc(), the behaviour is undefined. Any use of a pointer that refers to freed space causes undefined behaviour.

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

2. «Указатель» это так же область всё той же памяти, но имеющая весьма специфичное назначение.

3. «Доктор, Вы уж определитесь — либо трусы натяните, либо крестик снимите». То у Вас п.2 это «высеры», то всё-таки там встречаются разумные мысли, пусть и «немного» (это я скрепя сердце переживу). Так вот. При вопросе на linux.org.ru мне что-то подсказывает что вопрос будет касаться именно linux. Ваши апелляции в треде к оффтопу здесь не имеют ни малейшего под собой основания. И всем совершенно по-барабану что именно там в оффтопе и как именно. Даже по-барабану как и что в соляре, которую я помянул в другом постинге. Как будет в Linux, я опять таки Вам расписал уже. Не благодарите... :)))

4. Ясно. У нас с Вами не только разные Linux... Но ещё и разные google... Нате — ftp://g.oswego.edu/pub/misc/malloc.c

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

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