Работа с массивом со знаком int, как если бы он содержал неподписанные значения - PullRequest
0 голосов
/ 14 июня 2019

Я унаследовал некоторый старый код, который предполагает, что int может хранить значения от -2 31 до 2 ^ 31 -1, это переполнение просто оборачивается, ичто знаковый бит является старшим битом.Другими словами, этот код должен был использовать uint32_t, за исключением того, что это не так.Я хотел бы исправить этот код для использования uint32_t.

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

struct data {
    int a[10];
};
void frobnicate(struct data *param);

Я хотел бы изменить int a[10] на uint32_t a[10], но мне не разрешено изменять определение struct data.

Я могу заставить код работать на uint32_t или unsigned внутри:

struct internal_data {
    unsigned a[10];
};
void frobnicate(struct data *param) {
    struct internal_data *internal = (struct internal_data *)param;
    // ... work with internal ...
}

Однако на самом деле это не правильно C, так как он преобразует указатели в разные типы.

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

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

Я по крайней мере собираюсь добавить

#if INT_MIN + 1 != -0x7fffffff
#error "This code only works with 32-bit two's complement int"
#endif

С этим защитником, что может пойти не так с приведенным выше приведением?Есть ли надежный способ манипулировать массивом int, как если бы его элементы были unsigned, без копирования массива?

В итоге:

  • Я не могу изменитьпрототип функции.Он ссылается на массив int.
  • Код должен манипулировать массивом (не копией массива) как массивом unsigned.
  • Код должен создаваться на платформах, гдеон работал раньше (по крайней мере, с достаточно дружественными компиляторами) и не должен собираться на платформах, где он не может работать.
  • Я не могу контролировать, какой компилятор используется и с какими настройками.

1 Ответ

1 голос
/ 14 июня 2019
  • Однако это на самом деле не правильно C, так как он приводит между указателями на разные типы.

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

    typedef union
     {
       struct   data;
       uint32_t array[10];
     } internal_t;
    
     ...
    
     void frobnicate(struct data *param) {
         internal_t* internal = (internal_t*)param;
         ...
    

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

     struct data {
       union {
         int        a[10];
         uint32_t u32[10];
       }
     };
    

    Это означает, что пользовательский код, обращающийся к foo.a, не сломается.Но вам потребуется C11 или новее.

    В качестве альтернативы, вы можете использовать uint32_t* для прямого доступа к int[10].Это также четко определено, поскольку uint32_t в данном случае является беззнаковым эквивалентом действующего типа int.


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

    очевидно, это static_assert(sizeof(int) == 4, "int is not 32 bits");, но опять же это требует C11.Если требуется обратная совместимость со старым C, вы можете придумать какой-нибудь грязный «статический утверждение бедняка»:

    #define stat_assert(expr) typedef int dummy_t [expr]; 
    

  • #if INT_MIN != -0x80000000

    В зависимости от того, насколько вы требовательны, это не на 100% портативно.int теоретически может составлять 64 бита, но, вероятно, переносимость к таким вымышленным системам также нежелательна.

    Если вы не хотите перетаскивать limit.h, вы также можете написать макрос как

    #if (unsigned int)-1 != 0xFFFFFFFF
    

    Это лучший макрос независимо от того, что в нем нет скрытых неявных * 1051 драгоценных камней . Обратите внимание, что -0x80000000 всегда на 100% эквивалентно 0x80000000 в 32-битной версии.система.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...