Какие проблемы с переносимостью связаны с доступом на уровне байтов к указателям в C?

objective

Я пишу небольшую библиотеку для более крупного проекта, который предоставляет функции malloc / realloc / free wrapper-функции, а также функцию, которая может рассказать вам, соответствует ли ее параметр (типа void * ) для выделенной (еще не освобожденной) памяти и управляется оберточными функциями библиотеки. Давайте isgood_memory к этой функции как isgood_memory .

Внутри библиотека поддерживает hash-таблицу, чтобы гарантировать, что поиск, выполненный isgood_memory , достаточно быстр. Хэш-таблица поддерживает значения указателя (элементы типа void * ), чтобы сделать поиск возможным. Ясно, что значения добавляются и удаляются из таблицы hashей, чтобы поддерживать ее в актуальном состоянии с тем, что было выделено и что было освобождено, соответственно.

Переносимость библиотеки – моя самая большая проблема. Он был разработан, чтобы предполагать только совместимую в основном среду C90 (ISO / IEC 9899: 1990) … ничего более.

Вопрос

Поскольку переносимость – моя самая большая проблема, я не мог предположить, что sizeof(void *) == sizeof(X) для hash-функции. Поэтому я прибегал к обработке байта по байтам, как если бы это была строка. Для этого хеш-функция выглядит примерно так:

 static size_t hashit(void *ptrval) { size_t i = 0, h = 0; union { void *ptrval; unsigned char string[sizeof(void *)]; } ptrstr; ptrstr.ptrval = ptrval; for (; i < sizeof(void *); ++i) { size_t byte = ptrstr.string[i]; /* Crazy operations here... */ } return (h); } 

С какими соображениями переносимости у кого-либо из вас есть этот конкретный fragment? Будут ли ptrval какие-либо проблемы с выравниванием ptrval к байт-байту ptrval ?

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

Почему они могут быть разными? Ну, во-первых, большинству типов данных C разрешено содержать биты дополнений, которые не участвуют в значении. Платформа, в которой указатели содержали такие биты заполнения, может иметь два указателя, которые отличаются только в битах заполнения, указывают на одно и то же местоположение. (Например, ОС может использовать некоторые биты указателя для указания возможностей указателя, а не только физического адреса.) Другим примером является модель с далекой памятью с первых дней DOS, где далеко указатели состоят из сегмента: смещения и смежных сегменты перекрываются, так что сегмент: смещение может указывать на то же место, что и сегмент + 1: смещение-x.

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

Выглядит довольно чисто. Если вы можете положиться на заголовок с C99 (его часто можно найти в другом месте), тогда рассмотрите возможность использования uintptr_t – но если вы хотите hash-значение по-умолчанию, вы в конечном итоге разбиваете вещи на байты, и есть нет реального преимущества для него.

В основном правильно. Однако есть одна потенциальная проблема. вы назначаете

 size_t byte = ptrstr.string[i]; 

* string определяется как char, а не unsigned char. На платформе с подписанными символами и без знака size_t это даст вам результат, который вы можете или не можете ожидать. Просто измените свой символ на unsigned char, который будет чище.

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

 #pragma pack(1) struct sMemHdl { int magic; byte firstByte; }; #pragma pack() #define MAGIC 0xDEADDEAD #define MAGIC_SIZE sizeof(((struct sMemHdl *)0)->magic) void *get_memory( size_t request ) { struct sMemHdl *pMemHdl = (struct sMemHdl *)malloc(MAGIC_SIZE + request); pMemHdl->magic = MAGIC; return (void *)&pMemHdl->firstByte; } void free_memory ( void *mem ) { if ( isgood_memory(mem) != 0 ) { struct sMemHdl *pMemHdl = (struct sMemHdl *)((byte *)mem - MAGIC_SIZE); pMemHdl->magic = 0; free(pMemHdl); } } int isgood_memory ( void *Mem ) { struct sMemHdl *pMemHdl = (struct sMemHdl *)((byte *)Mem - MAGIC_SIZE); if ( pMemHdl->magic == MAGIC ) { return 1; /* mem is good */ } else { return 0; /* mem already freed */ } } 

Это может быть немного хакерским, но я думаю, что я в хакерском настроении …

Доступ к переменным таким целым числам или указателям, как символы или неподписанные символы, не является проблемой из представления о переносимости. Но обратное неверно, потому что оно зависит от оборудования. У меня есть один вопрос: почему вы хешируете указатель как строку вместо того, чтобы использовать сам указатель как значение hashа (используя uintptr_t)?