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

Цель

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

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

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

Вопрос

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

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);
}

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


person Community    schedule 12.06.2009    source источник
comment
порядок байтов потенциально может быть проблемой   -  person cobbal    schedule 13.06.2009
comment
Не совсем; это только для хеширования внутри программы, поэтому порядок байтов фиксирован, и не имеет значения, какой порядок байтов применяется.   -  person Jonathan Leffler    schedule 13.06.2009
comment
Я только что перенес его с платформы с прямым порядком байтов на платформу с прямым порядком байтов (сервер Sun Microsystems, к которому у меня есть доступ). Кажется, все работает нормально.   -  person    schedule 13.06.2009
comment
Порядок следования байтов почти наверняка изменит хеш-значение, вычисленное для того же значения указателя, но поскольку у вас не будет того же значения указателя на любой другой платформе (вообще говоря), это должно не быть проблемой. Если это так, у вас другая проблема.   -  person RBerteig    schedule 13.06.2009


Ответы (5)


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

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

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

person Community    schedule 12.06.2009
comment
Могу ли я составить разумный список таких платформ? Есть ли у них какие-либо определяющие характеристики, которые (например) помогли бы мне с поиском в Google? - person ; 13.06.2009
comment
Вам нужен список платформ, на которых шаблоны битов указателя уникальны или не уникальны? Я не думаю, что есть общие термины, которые можно было бы использовать для поиска, но в целом основные платформы, подобные рабочим станциям (под управлением таких ОС, как Windows, Solaris, HP-UX, Linux), как правило, имеют плоские адресные пространства и уникальные битовые шаблоны. Во встроенном мире или с экспериментальными машинами вы можете получить больше нестандартного оборудования, и все может быть по-другому. Стандарт C допускает такие платформы, но я не знаю, сколько таких платформ используется сегодня. - person dewtell; 15.06.2009
comment
(Это может быть глупый вопрос, но мне нужно быть уверенным.) Предположим, что у нас есть два значения-указателя (оба типа void *), названные A и B. Может ли когда-нибудь быть случай, когда A и B имеют один и тот же базовый битовый шаблон, но на самом деле ссылаются на разные места в памяти? - person ; 03.07.2009
comment
Если вам нужен абсолютно окончательный ответ, я не тот человек, которого стоит спрашивать. Вы можете задать вопрос в группе новостей comp.std.c, где есть гораздо больше юристов по языковым вопросам, некоторые из которых работали в комитете, разрабатывавшем стандарт. Но я уверен, что ответ отрицательный, и ответ будет основан на той части стандарта, которая определяет значения объектов. У вас могут быть биты объекта, которые не участвуют в значении, но я не думаю, что есть какое-либо основание для значения, которое требует большего, чем биты. Слишком много таких вещей, как memcpy, сломалось бы, если бы это было ложью. - person dewtell; 08.07.2009

Выглядит довольно чистым. Если вы можете полагаться на заголовок <inttypes.h> из C99 (он часто доступен в другом месте), рассмотрите возможность использования uintptr_t, но если вы хотите хэшировать значение побайтно, вы в конечном итоге разбиваете все на байты, и нет никакого реального преимущества для Это.

person Community    schedule 12.06.2009
comment
Заголовочный файл C99, определяющий uintptr_t, называется ‹stdint.h›, а не ‹inttypes.h›; просто так получилось, что ‹inttypes.h› включает ‹stdint.h›. ‹inttypes.h› определяет тип imaxdiv_t и несколько макросов для спецификаторов формата, которые будут использоваться с printf/scanf для различных целочисленных типов из ‹stdint.h›. - person Adam Rosenfield; 13.06.2009
comment
Определено, что ‹inttypes.h› включает ‹stdint.h›, и гораздо более вероятно, что у вас есть ‹inttypes.h› без ‹stdint.h›, чем ‹stdint.h›, а не ‹inttypes. час>. Но да, вы правы, если у вас есть только ‹stdint.h›, то вам не нужен ‹inttypes.h›. - person Jonathan Leffler; 13.06.2009

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

size_t byte = ptrstr.string[i];

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

person Community    schedule 12.06.2009
comment
Спасибо за предложение. Забавно... На самом деле у меня в коде есть беззнаковый символ, но я пропустил беззнаковый, когда набирал его здесь. Я исправил приведенный выше фрагмент кода. - person ; 13.06.2009

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

#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 */
   }
}

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

person Community    schedule 13.06.2009

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

person Community    schedule 13.06.2009
comment
Доступен ли uintptr_t в C90 (ISO/IEC 9899:1990)? - person ; 14.06.2009
comment
uintptr_t не является частью стандарта C90, но поддерживается многими компиляторами и определен в stdint.h. - person bill; 14.06.2009
comment
Более крупный проект довольно строго соответствует стандарту C90, поэтому у меня осталось несколько других вариантов. - person ; 14.06.2009
comment
Всегда есть возможность назначить/присвоить указатель целому числу. - person bill; 14.06.2009