Подтвердить что ты не робот

Как отличить строки в куче или литералах?

У меня есть прецедент, где я могу получить указатели на строки, выделенные либо в памяти, либо в литералах. Теперь последний не может быть освобожден, так что проблема, если я пройду неправильно. Есть ли способ узнать, какой из них выделен, а какие нет?

char *b = "dont free me!";
if(!IS_LITERAL(b)) {
    free(b);
}

Я представляю что-то подобное.

Мой пример:

Сценарий 1: буквальный

char *b = "dont free me!";
scruct elem* my_element = mylib_create_element(b);
// do smth
int result = mylib_destroy_element(my_element); // free literal, very bad

Сценарий 2: в куче

char *b = malloc(sizeof(char)*17); // example
strncpy(b, "you can free me!",17);

scruct elem* my_element = mylib_create_element(b);
// do smth
int result = mylib_destroy_element(my_element); // free heap, nice

То, как пользователь вызывает mylib_create_element(b);, не находится под моим контролем. Если он освобождается до mylib_destroy_element, он может сработать. Таким образом, это должно быть mylib_destroy_element, которое очищает.

4b9b3361

Ответ 1

Недавно у меня был похожий случай. Вот что я сделал:

Если вы создаете API, который принимает указатель на строку, а затем использует его для создания объекта (mylib_create_element), хорошей идеей было бы скопировать строку в отдельный буфер кучи, а затем освободить ее по своему усмотрению, Таким образом, пользователь отвечает за освобождение строки, которую он использовал при вызове вашего API, что имеет смысл. Это его строка, в конце концов.

Обратите внимание, что это не будет работать, если ваш API зависит от пользователя, изменяющего строку после создания объекта!

Ответ 2

В большинстве Unix есть значения "etext" и "edata". Если ваш указатель находится между "etext" и "edata", он должен быть статически инициализирован. Эти значения не упоминаются ни в одном стандарте, поэтому использование не переносится и на свой страх и риск.

Пример:

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

extern char edata;
extern char etext;

#define IS_LITERAL(b) ((b) >= &etext && (b) < &edata)

int main() {
    char *p1 = "static";
    char *p2 = malloc(10);
    printf("%d, %d\n", IS_LITERAL(p1), IS_LITERAL(p2));
}

Ответ 3

Вы можете только попросить пользователя явно указать свой ввод как литерал или выделенную строку.

Однако, как упоминает в своем ответе @Mints97, в основном этот подход является архитектурно неправильным: вы принудительно используете пользователя своей библиотеки для некоторых явных действий, и, если он забывает об этом, он скорее всего приведет к утечке памяти (или даже к крах приложения). Поэтому используйте его, только если:

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

Пример реализации

#define AAS_DYNAMIC             'D'
#define AAS_STATIC              'S'

#define AAS_STATIC_PREFIX       "S"
#define AAS_CONST_STR(str)      ((AAS_STATIC_PREFIX str) + 1)

char* aas_allocate(size_t count) {
    char* buffer = malloc(count + 2);
    if(buffer == NULL)
        return NULL;

    *buffer = AAS_DYNAMIC;

    return buffer + 1;
}

void aas_free(char* aas) {
    if(aas != NULL) {
        if(*(aas - 1) == AAS_DYNAMIC) {
            free(aas - 1);
        }
    }
}

...

char* s1 = AAS_CONST_STR("test1");
char* s2 = aas_allocate(10);

strcpy(s2, "test2");

aas_free(s1);
aas_free(s2);

Производительность тестирования (примечание №1)

Я сравнил свою библиотеку libsjson со следующим кодом (итерации 800k):

    node = json_new_node(NULL);
    json_add_integer(node, NODE_NAME("i"), 10);
    json_add_string(node, NODE_NAME("s1"), json_str_create("test1"));
    json_add_string(node, NODE_NAME("s2"), json_str_create("test2"));
    json_node_destroy(node);

Мой процессор - это Intel Core i7 860. Если NODE_NAME - всего лишь макрос, время на итерацию составляло 479 нс Если NODE_NAME - распределение памяти, время на итерацию равно 609ns

Подсказка пользователя или компилятора (примечание # 2)

  • Добавьте подсказку ко всем таким указателям, то есть анализатор статического источника Linux Sparse может поймать такие проблемы.

    char __autostring* s1 = aas_copy("test"); /* OK */
    char __autostring* s2 = strdup("test");   /* Should be fail? */
    char* s3 = s1;                            /* Confuses sparse */
    char* s4 = (char*) s1;                    /* Explicit conversion - OK */
    

(не совсем уверен в выводах Sparse)

  • Используйте простой typedef, чтобы заставить компилятор вызывать предупреждение, когда вы делаете что-то не так:

    #ifdef AAS_STRICT
    typedef struct { char a; } *aas_t;
    #else
    typedef char *aas_t;
    #endif
    

Этот подход является еще одним шагом к миру грязных C-хаков, т.е. sizeof(*aas_t) теперь > 1.

Полный источник с изменениями можно найти здесь. Если скомпилировано с помощью -DAAS_STRICT, это вызовет массу ошибок: https://ideone.com/xxmSat Даже для правильного кода он может жаловаться на strcpy() (не воспроизводится на ideone).

Ответ 4

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

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

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

Ответ 5

Вот практический способ:

Хотя стандарт C-language не поддерживает, это означает, что для всех идентичных вхождений данной строки литерала в ваш код компилятор генерирует одну копию в секции RO-данных исполняемого образа.

Другими словами, каждое вхождение литеральной строки "dont free me!" в вашем коде переводится в один и тот же адрес памяти.

Итак, в тот момент, когда вы хотите освободить эту строку, вы можете просто сравнить его адрес с адресом литеральной строки "dont free me!":

if (b != "dont free me!") // address comparison
    free(b);

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


Вышеизложенное представляет собой просто практический трюк, непосредственно относящийся к рассматриваемому вопросу (а не к мотивации этого вопроса).

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

Ответ 6

Вы можете сделать следующее:

  typedef struct 
{
 int is_literal;
 char * array;
} elem;

Каждый раз, когда вы выделяете elem.array в куче, просто устанавливайте is_literal равным 0. Когда вы задаете массив как литерал, установите флаг в ненулевое значение, например:

elem foo;
foo.array = "literal";
foo.is_literal = 1 ;

или

elem bar;
bar.array = (char*) (malloc(sizeof(char) * 10)) ;
bar.is_literal = 0;

Затем на стороне клиента:

if(!bar.is_literal) {
free(bar.array);
}

Просто как это.

Ответ 7

Именно поэтому правило состоит в том, что только часть кода или модуля, который создал строку, может освободить ее. Другими словами, каждая строка или фрагмент данных "принадлежит" блоком кода, который его создал. Только владелец может его освободить. Функция никогда не должна освобождать структуры данных, которые она получила в качестве аргументов.

Ответ 8

Еще в первые дни, когда 80386 мог иметь максимум 8 мегабайт оперативной памяти, а идеи создания объектов объяснялись в каждой другой статье в журналах, мне не нравилось копировать совершенно хорошие литералы в строковые объекты (выделяя и освобождая внутренняя копия), и я спросил об этом Бьярне, поскольку грубый класс строк был одним из его примеров фантазийного материала С++.

Он сказал, не волнуйся об этом.

Это связано с литералами против других указателей char*? Вы всегда можете хранить память. Подумайте так, исходя из ваших идей поиска разных сегментов памяти.

Или это в общем случае то, что собственность может быть или не быть предоставлена, нет способа сказать, и нужно хранить флаг: "Эй, это кучный объект, но кто-то еще его использует и будет позаботься об этом позже, ОК?"

Для доступного случая, когда он находится "на куче" или "нет" (литералы, глобалы, основанные на стеке), вы могли бы знать free функцию. Если вы предоставили соответствующий набор allocate/maybe-free, его можно было бы написать, чтобы узнать, какая память находится под его контролем.