Как определить, является ли память динамической или статической с точки зрения вызываемого абонента?

Примечание: когда я говорю «статическая строка», я имею в виду память, которая не может быть обработана realloc.

Привет, я написал процедуру, которая принимает аргумент char *, и я хотел бы создать дубликат, если память не может быть перемещена/изменена через realloc. Как есть, процедура представляет собой «тяжелый» процессор строк, поэтому неосведомленность и дублирование строки, независимо от того, является ли она статической, наверняка вызовет некоторые проблемы с памятью/обработкой в ​​​​будущем.

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

Я пытался использовать обработчики исключений для вызова realloc для статической переменной... Glib сообщает, что не может найти некоторую личную информацию для структуры (я уверен), о которой я не знаю, и, очевидно, вызывает прерывание программы, которая означает, что это не исключение, которое можно поймать с помощью longjmp/setjmp ИЛИ C++ try, catch finally.

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

Я не уверен, есть ли какие-либо макросы в препроцессорах C/C++, которые могут идентифицировать источник и тип аргумента макроса, но было бы довольно глупо, если бы этого не было. Макроассемблеры довольно умны в таких вещах. Судя по отсутствию надежной обработки ошибок, я бы ничуть не удивился, если бы это было не так.


person Community    schedule 11.04.2012    source источник
comment
Чтобы было ясно из смещения: нет никакого способа сделать это независимым от платформы способом.   -  person Oliver Charlesworth    schedule 11.04.2012
comment
Я пытался использовать обработчики исключений для изменения статической строки, но приложение просто закрывалось без какого-либо уведомления. Я отступаю, смотрю на С и говорю: я не впечатлен. Это было бы исключением, если бы я когда-либо слышал об одном. - Уилл, учитывая, что в C нет понятия исключения, которое можно обработать (т. е. нет такой вещи, как обработчик исключений), я думаю, что это довольно разумно. . Как вы ожидаете справиться с изменением памяти только для чтения?   -  person Ed S.    schedule 11.04.2012
comment
Это вопрос или прикол?   -  person geekosaur    schedule 11.04.2012
comment
Ух ты! Никогда не получал столько ОТЛИЧНЫХ ответов сразу!   -  person    schedule 11.04.2012
comment
@TristonJ.Taylor: Из ваших комментариев у меня сложилось впечатление, что вы пишете код на C. Вам нужны ответы на C или C++?   -  person Mooing Duck    schedule 11.04.2012
comment
Я хотел бы использовать C, но если можно найти решение C++... Мои файлы уже в *.cpp: D   -  person    schedule 11.04.2012
comment
@ЭдС. В свое время я бы вызвал ядро ​​Windows, используя машинную сборку, чтобы установить нужные флаги. и или мог бы написать менеджер списков, подобный тому, который описан в моем компиляторе строк в моем ответе на этот вопрос. Который использует подсчет параметров для проверки работоспособности с помощью API.   -  person    schedule 11.04.2012


Ответы (5)


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

Если вы контролируете все распределение, вы можете просто сохранить минимальные и максимальные границы на основе каждого динамического указателя, который когда-либо выходил из malloc, calloc или realloc. Указатель ниже min или больше max, вероятно, не находится в куче, и эта область с разделителями min и max вряд ли когда-либо пересекается с какой-либо статической областью. Если вы знаете, что указатель либо статичен, либо получен от malloc, и этот указатель находится за пределами «ограничивающей рамки» хранилища с распределенным доступом, то он должен быть статическим.

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

person Kaz    schedule 11.04.2012
comment
Это мой любимый ответ! Я могу сказать, что у вас сумасшедший опыт с музейными экспонатами C.! Классно! Как бы мне не хотелось это признавать: эксперты подтвердили! Документация + квалификация типа — единственный способ пойти сюда (сегодня). - person ; 11.04.2012
comment
Спасибо, сэр, вы дали адекватный ответ на вопрос. - person ; 11.04.2012
comment
Еще одна вещь, которую я недавно сделал в интерпретаторе для языка TXR, — это реализация нескольких типов строк. Все они могут использоваться взаимозаменяемо, но сборщик мусора знает, что нельзя наступать на статические. Они идентифицируются двухбитным кодом типа, вставленным прямо в указатель. Макрос lit("foo bar") берет указатель wchar_t *, созданный строковым литералом, и изменяет два младших значащих бита на код типа литерала. Динамические строки, с другой стороны, являются указателями на более сложную структуру, распределенную в куче. Их можно использовать взаимозаменяемо. - person Kaz; 11.04.2012
comment
Такой подход НЕ ДЕЙСТВИТЕЛЬНЫЙ. Даже если вы предполагаете, что сравнение указателей между отдельными объектами работает, вполне возможно, что строковый литерал или другая строка со статической продолжительностью хранения появится между двумя malloc полученными строками. Это произойдет всякий раз, когда статическая строка находится в общей библиотеке, а две строки, полученные malloc, оказались размещены на противоположных сторонах (например, через mmap) области, в которую была загружена общая библиотека. Вопрос ОП просто в корне ошибочен, но представление этого сломанного и опасного ответа нехорошо, даже если оно принесет вам некоторую репутацию ... - person R.. GitHub STOP HELPING ICE; 11.04.2012
comment
Действительно, подход должен быть точно настроен для использования совместно используемой библиотеки, когда вы находитесь прямо посреди виртуальной памяти. - person Kaz; 11.04.2012
comment
@R.. Очевидно, я резидент F.N.G. но я понял, что было сказано довольно четко, что это может работать в теории. Я полностью ценю участие всех в этой теме, и многие хорошие идеи, которые я здесь почерпнул, останутся со мной надолго. Что, вероятно, наиболее важно, так это то, что эта информация будет доступна для других, чтобы они могли внести свой вклад и поучиться у нее. Спасибо за ваш вклад! Как правило, приятно участвовать в совместной дискуссии по актуальным интересующим темам. - person ; 11.04.2012

C не предоставляет портативного способа отличить статически выделенные блоки памяти от динамически выделенных. Вы можете создать свой собственный struct со строковым указателем и флагом, указывающим тип памяти, занимаемой объектом. В C++ вы можете сделать его классом с двумя разными конструкторами, по одному на каждый тип памяти, чтобы упростить себе жизнь.

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

person Sergey Kalinichenko    schedule 11.04.2012
comment
Большое спасибо за ваш вклад! Я согласен с Оли: лучший способ сделать это на C. - person ; 11.04.2012
comment
хотя я бы отказался от структуры и просто использовал typedef. Нет смысла тратить память, если подойдет проверка типа препроцессора. - person ; 11.04.2012
comment
@TristonJ.Taylor: это нельзя решить с помощью typedef (по крайней мере, не в C, где функции нельзя перегружать). - person Oliver Charlesworth; 11.04.2012
comment
Это, безусловно, заставит вызывающую сторону использовать недопустимый указатель. Это то, от чего я могу их защитить в документации. Это единственная функция, которая должна ожидать тип и возвращать тот же тип. Поэтому необходимо будет реализовать функции-оболочки для free, malloc и realloc. Вишенкой на торте является макрос, который может преобразовать тип обратно в char * - person ; 11.04.2012
comment
за исключением оберток... все это по сути бесплатно. - person ; 11.04.2012

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

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

person MSN    schedule 11.04.2012
comment
Я не понимаю, как возврат null из-за недопустимого указателя является условием, из которого нельзя вернуться. Это имеет смысл, если бы мы коверкали стек или делали какие-то инъекции кода, но не в доступной пользователю памяти ввода-вывода «загромождения». Я полагаю, я должен быть благодарен, что это выходит из строя с шумом. На один отчет об ошибке меньше, который мне придется создать. Но это отстой, что это делает «приложение-бум» - person ; 11.04.2012
comment
Да, верните ноль, установите код ошибки EINVALID_MPTR, и теперь у вас есть способ ДОСТОВЕРНО определять статическую или динамическую память. Однако похоже, что это какая-то функция безопасности «сумасшедшего кода». Только охранники такие грубые! - person ; 11.04.2012
comment
лол Знакомо? Извините, ваш звонок не может быть завершен как набранный... Соедините две темы: Ваше обслуживание будет прекращено безвозвратно. - person ; 11.04.2012
comment
Что случилось, чтобы попытаться позвонить еще раз? Это просто грубо! - person ; 11.04.2012

Я написал процедуру, которая принимает аргумент char *, и я хотел бы создать дубликат, если память не может быть перемещена/изменена через realloc.

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

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

Как уже упоминалось, C не имеет исключений. C++ может сделать это, но комитет по стандартам C++ считает, что если функции C ведут себя по-другому в C++, это будет кошмаром.

Я почти уверен, что должен быть способ сделать это разумно.

Ваше приложение может заменить стек по умолчанию созданным вами (и, таким образом, знать диапазон адресов), используя ucontext.h или Windows Fibers и проверьте, входит ли адрес в этот диапазон. Однако (1) это создает огромную нагрузку на любое приложение, использующее вашу библиотеку (конечно, если вы написали единственное приложение, использующее вашу библиотеку, вы можете принять это бремя); и (2) не обнаруживает память, которая не может быть realloced по другим причинам (выделена с помощью static, выделена с помощью пользовательского распределителя, выделена с помощью SysAlloc или HeapAlloc в Windows, выделена с помощью new в C++ и т. д.).

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

person Max Lybbert    schedule 11.04.2012
comment
Мне нравится твой стиль. Спасибо за отличный ответ на мой вопрос. - person ; 11.04.2012

оригинальный постер здесь. Я забыл упомянуть, что у меня есть рабочее решение проблемы, оно не такое надежное, как я надеялся. Пожалуйста, не расстраивайтесь, я ценю всех, кто принял участие в этом запросе комментариев и ответов. «Процедура» in question по своей природе является variadic и предполагает не более 63 анонимных char * аргументов.

Что это такое: конкатенатор нескольких строк. Он может обрабатывать множество аргументов, но я советую разработчику не передавать более 20 или около того. Разработчик никогда не вызывает процедуру напрямую. Вместо этого макрос, известный как «имя процедуры», передает аргументы вместе с конечным нулевым указателем, поэтому я знаю, когда я встретил конец сбора статистики.

Если функция получает только два аргумента, я создаю копию первого аргумента и возвращаю этот указатель. Это строковый литерал. Но на самом деле все, что он делает, это маскирует strdup

Не пройдя проверку единственного допустимого аргумента, мы переходим к realloc и memcpy, используя информацию о записи из статической базы данных из 64 записей, содержащих каждый указатель и его strlen, каждый раз добавляя размер memcopy к вторичному указателю (memcpy destination), который начинался как копия возвращаемого значения из realloc.

Я написал второй макрос с придатком «d», чтобы указать, что первый аргумент не является динамическим, поэтому требуется динамический аргумент, и этот макрос использует следующий код для внедрения динамического аргумента в фактический вызов процедуры как первый аргумент:

strdup("")

Это действительный блок памяти, который можно перераспределить. Его strlen возвращает 0, поэтому, когда цикл добавляет его размер к записям, он ни на что не влияет. Нулевой терминатор будет перезаписан на memcpy. Работает чертовски хорошо, надо сказать. Однако, будучи новичком в C всего за последние несколько недель, я не понимал, что вы не можете «защитить от дурака» этот материал. Я полагаю, люди следуют указаниям или попадают в ад DLL.

Код отлично работает без всех этих дополнительных махинаций и свистков, но без способа возвратно-поступательного движения одного блока памяти процедура теряется при обработке цикла из-за всего управления динамическими указателями. вовлеченный. Поэтому первый аргумент всегда должен быть динамическим. Я где-то читал, что кто-то предложил использовать переменную c-static, содержащую указатель в функции, но тогда вы не можете использовать эту процедуру, чтобы делать другие вещи в других функциях, например, что было бы необходимо в синтаксическом анализаторе рекурсивного спуска, который решил скомпилировать струны по ходу дела.

Если вы хотите увидеть код, просто спросите!

Удачного кодирования!


mkstr.cpp

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

struct mkstr_record {
    size_t size;
    void *location;
};

// use the mkstr macro (in mkstr.h) to call this procedure.
// The first argument to mkstr MUST BE dynamically allocated. i.e.: by malloc(),
// or strdup(), unless that argument is the sole argument to mkstr. Calling mkstr()
// with a single argument is functionally equivalent to calling strdup() on the same
// address.
char *mkstr_(char *source, ...) {

    va_list args;

    size_t length = 0, item = 0;

    mkstr_record list[64]; /*

    maximum of 64 input vectors. this goes beyond reason!

    the result of this procedure is a string that CAN be
    concatenated by THIS procedure, or further more reallocated!

    We could probably count the arguments and initialize properly,
    but this function shouldn't be used to concatenate more than 20
    vectors per call. Unless you are just "asking for it".

    In any case, develop a workaround. Thank yourself later.

    */// Argument Range Will Not Be Validated. Caller Beware!!!

    va_start(args, source);

    char *thisArg = source;

        while (thisArg) {

            // don't validate list bounds here.
            // an if statement here is too costly for
            // for the meager benefit it can provide.

            length += list[item].size = strlen(thisArg);
            list[item].location = thisArg;
            thisArg = va_arg(args, char *);
            item++;

        }

    va_end(args);

    if (item == 1) return strdup(source);   // single argument: fail-safe

    length++;   // final zero terminator index.

    char *str = (char *) realloc(source, length);

    if (!str) return str;   // don't care. memory error. check your work.

    thisArg = (str + list[0].size);

    size_t count = item;

    for (item = 1; item < count; item++) {
        memcpy(thisArg, list[item].location, list[item].size);
        thisArg += list[item].size;
    }

    *(thisArg) = '\0';  // terminate the string.

    return str;

}


mkstr.h

#ifndef MKSTR_H_
#define MKSTR_H_

extern char *mkstr_(char *string, ...);

// This macro ensures that the final argument to "mkstr" is null.
// arguments: const char *, ...
// limitation: 63 variable arguments max.
// stipulation: caller must free returned pointer.

#define mkstr(str, args...) mkstr_(str, ##args, NULL)
#define mkstrd(str, args...) mkstr_(strdup(str), ##args, NULL)

/* calling mkstr with more than 64 arguments should produce a segmentation fault
 * this is not a bug. it is intentional operation. The price of saving an in loop
 * error check comes at the cost of writing code that looks good and works great.
 *
 * If you need a babysitter, find a new function [period]
*/


#endif /* MKSTR_H_ */

Не за что упоминать меня в титрах. Она в порядке и денди.

person Community    schedule 11.04.2012
comment
В ретроспективе полученного опыта. Возможно, было бы лучше предоставить общий конкатенатор строк, который strdup("")-realloc-memcpy объединяет все исходящие аргументы. И тот, который разработан специально для внутрицикловой обработки с положительно идентифицированным ответом. Я думаю, что этот подход поможет нарисовать более ясную картину того, что кажется мне ужасно двусмысленным для неизвестного. - person ; 11.04.2012
comment
провалиться с counters для создания counts, а использование address locations with counts в моей книге довольно сложное программирование. но опять же, я как раз сейчас работаю с C. - person ; 11.04.2012