Какие проблемы переносимости связаны с доступом на уровне байтов к указателям в C? - PullRequest
2 голосов
/ 13 июня 2009

Назначение

Я пишу небольшую библиотеку для большого проекта, которая предоставляет функции-оболочки 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 побайтно?

Ответы [ 5 ]

1 голос
/ 13 июня 2009

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

size_t byte = ptrstr.string[i];

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

1 голос
/ 13 июня 2009

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

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

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

1 голос
/ 13 июня 2009

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

0 голосов
/ 13 июня 2009

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

0 голосов
/ 13 июня 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 */
   }
}

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

...