Битовые операции определены и переносимы для целых чисел фиксированной ширины со знаком? - PullRequest
2 голосов
/ 24 апреля 2020

Насколько я понимаю, некоторые побитовые операции с обычными старыми типами short / int / long либо зависят от реализации (| & ~ >>), либо не определены (<<) </p>

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

Означает ли это, что все побитовые операции хорошо определены и переносимы для тех типы среди платформ, которые их предоставляют?

Например, это работает на моей машине ™, но гарантированно ли оно работает?

#include <inttypes.h>
#include <stdio.h>

int main() {
  uint16_t a = 0xffff;
  int16_t b = *(int16_t*)(&a);

  printf("%" PRId16 "\n", b);

  // Prints '-1'

  b <<= 4;

  printf("%" PRId16 "\n", b); 

  // Prints '-16'

  return 0;
}

1 Ответ

5 голосов
/ 24 апреля 2020

Использование типов фиксированной ширины не гарантирует защиту от неопределенного поведения , связанного со сдвигом битов. Раздел 7.20.1.1 C стандарта относительно точных состояний целочисленных типов ширины:

1 Имя typedef intN_t обозначает целочисленный тип со знаком с шириной N, без дополнительных битов и представления дополнения до двух. Таким образом, int8_t обозначает такой целочисленный тип со знаком с шириной ровно 8 бит.

2 Имя typedef uintN_t обозначает целочисленный тип без знака с шириной N и без битов заполнения. , Таким образом, uint24_t обозначает такой целочисленный тип без знака с шириной ровно 24 бита.

3 Эти типы являются необязательными. Однако если реализация предоставляет целочисленные типы с шириной 8, 16, 32 или 64 бита, без битов заполнения и (для типов со знаком), которые имеют представление дополнения до двух, она должна определить соответствующие имена typedef.

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

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

Например, предполагая 32-битное int, этот код демонстрирует неопределенное поведение:

uint24_t x = 0xffffff;
uint24_t y = x << 8;

Поскольку в выражении x << 8 значение x повышается до int, то сдвиг вызывает сдвиг бита в знаковый бит этого значения.

...