Может ли реализация структуры данных узнать, находится ли она в куче или нет?

Рассмотрим основной файл и другой файл, реализующий структуру данных (например, связанный список).

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

Итак, при реализации связанного списка, как он узнает, находится он в куче или нет? Рассмотрим типичный «метод», который удаляет узел из списка. Как связанный список узнает, следует ли освобождать эту память? Насколько я понимаю, освобождение чего-либо в стеке вызывает неопределенное поведение.

Поскольку это часть проекта класса, я не могу передать что-то (isOnHeap), чтобы указать, поместил ли вызывающий объект память в кучу (пояснение: невозможно, поскольку в нашей реализации это не допускается), поэтому я ' m при условии, что у этой проблемы может быть общее решение, особенно с учетом того, насколько распространенным будет такой случай. Обратите внимание, что реализация связанного списка должна обрабатывать освобождение собственной памяти (предполагается, что это дано, поскольку ее реализация скрыта от вызывающего).


person Spag Guy    schedule 05.10.2014    source источник
comment
Если это каким-то образом важно для кода структуры данных (но почему?), То такое должно быть с самого начала спроектировано в API.   -  person user2864740    schedule 05.10.2014
comment
Тег указывает, что это вопрос, связанный с C (добавлен C к заголовку). Я действительно видел этот вопрос. Однако, пожалуйста, рассмотрите контекст проблемы.   -  person Spag Guy    schedule 05.10.2014


Ответы (4)


Вот простая функция is_on_stack (addr):

int *stack_start = nullptr;

bool is_on_stack(void* ptr) {
  int stack_probe;  
  if( stack_start < &stack_probe  )
    return stack_start < ptr && ptr < &stack_probe; // stack grows upwards;
  else 
    return &stack_probe < ptr && ptr < stack_start; // stack grows downwards;
}

int main() {
  int stack_probe = 0;
  stack_start = &stack_probe;

  int dummy;

  bool r1 = is_on_stack(&dummy); // shall be true

  int* heap_thing = (int*)malloc(sizeof(int));

  bool r2 = is_on_stack(heap_thing); // shall be false         
}

Ключевым моментом здесь являются следующие строки в начале функции main ():

  int stack_probe = 0;
  stack_start = &stack_probe;
person c-smile    schedule 05.10.2014
comment
Спасибо, но я нашел возможное альтернативное решение, поэтому я собираюсь еще раз спросить о чем-то более конкретном. Поскольку это технически ответ на вопрос, я выберу его в качестве ответа для всех, кто сочтет это полезным. - person Spag Guy; 05.10.2014
comment
@ c-smile - Я думаю, у вас в коде ошибка. В is_on_stack вы выделяете stack_probe как int, а затем сравниваете его (неопределенное) содержимое с stack_start, который является указателем на int. Это явно неверно. Я думаю, вы хотите использовать &stack_probe вместо stack_probe во всех тестах этой функции. - person DoxyLover; 05.10.2014
comment
Кроме того, поправьте меня, если я ошибаюсь, я считаю, что это неопределенное поведение в соответствии со спецификацией C. Вы сравниваете указатели на разные объекты. Хотя я согласен с тем, что это должно работать в большинстве реализаций C, я чувствую, что должен указать на это. - person DoxyLover; 05.10.2014
comment
@DoxyLover, согласен с тобой. Решение может не работать в некоторых реализациях, таких как некоторый процессор, Big Endian, направление роста стека, многопоточность ..., - person Nebula Era; 05.10.2014
comment
@DoxyLover спасибо, исправлено - person c-smile; 05.10.2014
comment
@NebulaEra Я считаю, что приведенный выше код правильно обрабатывает направленность стеков. И что считать основным файлом ... далек от области многопоточности. На самом деле да, я бы решил все это иначе. - person c-smile; 05.10.2014
comment
@ c-smile, я не обратил особого внимания. Это действительно учитывает направление стека. Прекрасная работа! - person Nebula Era; 05.10.2014

Реализация структуры данных на C не должна ничего освобождать, если это не было явно сказано. Типичная реализация связанного списка может иметь такие функции, как «создать узел», «вставить узел в список здесь», «удалить узел из списка» и «уничтожить узел». Ни один из них не требует проверки стека или кучи. Обычно единственное, что может быть в стеке, - это указатели на начало и конец всего списка; даже то, что вы также можете выделять и освобождать функции API, которые вы разрабатываете (и использовать кучу).

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

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

person John Zwinck    schedule 05.10.2014
comment
Тогда я немного смущен, потому что типичный пример освобождения памяти, который мы изучаем, - это убедиться, что структура связанного списка повторяется и освобождает всю выделенную память. Если реализация списка скрыта от вызывающей стороны, то как вызывающая сторона узнает, как освободить любые данные на узлах, которые были выделены в куче? Между прочим, я не оспариваю вашу точку зрения, это искренний вопрос. Для меня более логично, что вызывающий должен брать на себя ответственность. Однако указанные нами функции удаления и уничтожения в нашей реализации списка инструктируют освободить динамическую память. - person Spag Guy; 05.10.2014

... Я не могу передать что-то (isOnHeap), чтобы указать, поместил ли вызывающий объект память в кучу

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

Пример проблемы можно увидеть в функциях Windows Net API. Например, предположим, что вы вызываете NetGroupEnum функция для перечисления групп. Библиотека выделит структуру GROUP_INFO_*. После того, как вызывающая сторона завершит работу со структурой, ее обязанность вызвать библиотеку функция NetApiBufferFree.

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

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

person jww    schedule 05.10.2014

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

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

person Nebula Era    schedule 05.10.2014
comment
Обычно - да. Но иногда более эффективно использовать данные в стеке. В некоторых случаях один и тот же список может содержать разные элементы - в куче и в стеке. Обычно элемент знает, как уничтожить себя (он имеет ссылку на функцию уничтожения или что-то подобное) ... - person c-smile; 05.10.2014