LINUX.ORG.RU

Си без UB/ID: Две кучи и GC, как?

 ,


0

6

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

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

Решение которое не работает в стандартом С:

bool in_heap(char *heap, ssize_t heap_size, char *p)
{
  return p >= heap && p < heap + heap_size;
}

А по стандарту как? Готов перейти на дескрипторы вместо указателей, но это не должно тормозить.

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

★★★★★

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

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

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

Учитываешь эти теги, в доке к компилятору должны быть описаны раз это ИБ

Тред называется «без UB/ID». Для каждой архитектуры не очень хочется писать по функции обрезки тега.

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

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

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

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

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

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

А как у тебя будут работать эти самые теггирование указатели в какой-то гипотетической архитектуре с аппаратной валидацией указателей? Да в той же сегментной модели

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

ты НИКАК не обойдёшься без синхронизации в многопоточном приложении.

Конечно, но можно снизить ее тормоза, сократить влияние, есть lock-free структуры, знаешь наверное? Но зачем то предлагаешь наоборот тормоза увеличивать, то мутексы добавить, то атомики дополнительные.

я думаю, что в сишечке оверхед в один байт никого бы не убил.

Байт под что? Под счетчик? Один элемент могут использовать и более 255 элементов других.

напиши прототип и посмотри, где у тебя будут затыки.

Уже написал, теперь делаю производительную версию.

в Си так не пишут.

В gcc есть сборщик мусора, rcu из ядра примерно тем же самым занимается.

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

Лучше тогда сравнить именно указатели. К ulong кастовать не стоит, уже есть RISC-V CHERI который не кастуется к ulong, нужно кастовать к uintptr_t который занимает более 8 байт, но там будут теги, я не уверен что это будет работать, а на милость компилятора при прямом сравнении указателей еще можно положиться.

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

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

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

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

но я боюсь что будет тормозить,

сравнение указателей или проверка бита с последующим удалением этого бита - примерно один хрен, и скорее всего потеряется на фоне GC и общей косвенности

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

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

чтобы охватить все архитектуры просто пишешь макру - pointer_in_pool (pointer, pool_min, pool_max), имеющую особенные смыслы в извращенческих железках. если оно так надо.

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

может у тебя два хипменеджера и оба с добавлением к себе новых пулов (дурное дело нехитрое ), тогда они будут по адресам вперемешку и сравнение не будет работать.

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

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

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

какие у тебы проблемы-то?

Нету, это тред-головоломка для решения простейшей задачи так, что бы решение не нарушало стандарт. Адекватное решение это набор всяких -fno-strict-aliasing и прямое сравнение указателей.

но их можно кастовать к нумерическим типам. и сравнивать.

На каких то можно, на каких то это не даст ничего полезного.

но в железках нормальных всю жизнь кастовали к числам и тупо сравнивали.

Ну неправда, близкие указатели сравнивать бесполезно.

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

я тоже не поняла, в каком «стандартном Си» это не работает.

Ну по стандарту же, я выше даже абзац из него скинул, где это написано. Абзац кстати в C11 переписали, так что он не древний вовсе.

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

слушай, покажи уже пальцем на тот «стандарт» о котором ты тут талдычишь. а то тебе тут куча сишников пишут, как оно работает на самом деле, а ты упёрся в какой-то никому не ведомый «стандарт» и отказываешься принимать очевидное и простое решение. может, ты не то прочитал, не так прочитал или вообще нашёл стандарт 1970-х годов, сишка уже и тогда была тоже, но системы были слегка другие.

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

Стандарт C23, страница 87, 6 пункт.

When two pointers are compared, the result depends on the relative locations in the address space
of the objects pointed to. If two pointers to object types both point to the same object, or both point
one past the last element of the same array object, they compare equal. If the objects pointed to
are members of the same aggregate object, pointers to structure members declared later compare
greater than pointers to members declared earlier in the structure, and pointers to array elements
with larger subscript values compare greater than pointers to elements of the same array with lower
subscript values. All pointers to members of the same union object compare equal. If the expression
P points to an element of an array object and the expression Q points to the last element of the same
array object, the pointer expression Q+1 compares greater than P. In all other cases, the behavior is
undefined.

Когда элементы в разных массивах, это other cases, а следовательно the behavior is undefined.

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

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

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

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

То есть делали полную арифметику. Именно преобразуя адреса в нумерическое представление, вычисляя и преобразуя обратно.

Это твои фантазии, что все нужно делать число на указателях.

Указатели вообще не для того, чтобы делать адресные вычисления, у них другое применение.

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

Какое именно поведение, того что in_heap будет работать? Оно гарантированно будет работать на intel64, в этом сомнений как раз нету. Виртуальное пространство он и отдаст, да только оно будет плоским и последовательным, и не будет пересекаться с другими malloc.

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

Ну по стандарту же, я выше даже абзац из него скинул, где это написано. Абзац кстати в C11 переписали, так что он не древний вовсе.

Лучше бы ты его в начале темы написал, а то сложно найти.

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

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

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

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

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

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

ты ж говоришь, это нарушает стандарт.

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

или на твоих специфических мегархитектурах оно не работет? ;)

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

Я знаю что это будет работать на intel64, и на Windows тоже. Мне интересно как это решить в рамках стандарта, что бы было переносимо и на экзотические платформы, уверен есть разные умные решения, которые мне будут интересны, пусть даже и не для практического применения.

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

Причём тут Страуструп? Ты баги себе устраиваешь, ssize_t недостаточен для представления произвольного размера выделенного блока и чреват переполнениями на ровном месте (это вообще странный тип, которого между прочим в твоём любимом стандарте вообще нет). А если ты хочешь ещё и работу на всех платформах - то это обстоятельство ещё многократно важнее становится (это на x86_32 и x86_64 диапазон size_t совпадает с размером адресного пространства, до даже там теоретически можно переполнить его половину, а на системах где size_t меньше чем вся память - упереться в него очень даже реально).

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

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

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

ты ж говоришь, это нарушает стандарт.

Это такое предложение забить на стандарт, если идет речь о реальном коде, а не о решении головоломок.

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

Эти встроенные функции не нарушают стандарт, они как writeln в Pascal, они нереализуемы на самом языке, builtin. Но за счет этого ничего не нарушают, имеют особые привилегии.

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

Это я для тебя специально добавил. В своем коде я не использую ssize_t, signed, int для размера и итераторов. А ssize_t вообще тип с подвохом, он не является signed полноценным, должен уметь только -1 принимать.

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

Недавно кстати обнаружил еще один бриллиант в стандарте, вот это условие в особо упоротом компиляторе не должно выполняться никогда:

int x;
int *p = &x;
p = p - p;
if (p == NULL)
  printf("Если в рантайме вышел 0, то это не NULL");

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

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

Конечно благодаря тому что С существует не в абстрактном мире комитета, а в реальном мире, на привычных нам компиляторах его все же можно реализовать, но вот универсальный переносимый memmove или malloc написать нельзя, который будет работать на любом предполагаемом компиляторе совместимым по стандарту на 100%.

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

У указателя NULL это особое состояние, которое он принимает только если ему передали NULL-constant. Так написано в стандарте, ну я упростил немного.

Из этого следует, что если он в рантайме принял состояние со значением 0, то в NULL состояние он не переходит.

При этом если присвоить указателю 0 как константу, то он примет NULL состояние.

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

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

firkax ★★★★★
()