Как получить доступ к байтовому представлению числа с плавающей точкой путем приведения к char * в C?

Я изучаю C, и частично для того, чтобы привыкнуть к указателям, а частично для того, чтобы узнать о плавающих числах IEEE 754, я попробовал следующее:

#include <stdio.h>

int main() {

    float f1 = -12.78;
    const char *p =  (char *)&f1;
    for(int i=0; i < sizeof(f1); i++) {
        printf("i: %i and p: %p and byte is %#x\n",i,p,*p);
        p++;
    }
}

Я не получаю никаких предупреждений или ошибок (в отличие от большинства версий, которые я пробовал раньше!), Но получаю странные результаты:

i: 0 and p: 0x16ce13368 and byte is 0xffffffe1
i: 1 and p: 0x16ce13369 and byte is 0x7a
i: 2 and p: 0x16ce1336a and byte is 0x4c
i: 3 and p: 0x16ce1336b and byte is 0xffffffc1

Мое (ограниченное) понимание IEEE 754 заключается в том, что -12,78 действительно представляет собой e1 7a 4c c1 в байтах, но почему первый байт отображается как ff ff ff e1 (и аналогично для последнего байта)?

Есть лучший способ сделать это?

Я также пробовал использовать массивы символов, но далеко не продвинулся. Я также пытался использовать void *p для доступа к памяти, содержащей число с плавающей запятой, но тоже не смог заставить это работать.

Используйте беззнаковый тип, чтобы избежать расширения знака для отрицательных чисел.

Retired Ninja 25.04.2024 18:39

Рассматривайте его не как указатель на символ, а как целое число. Затем вы можете извлечь части числа путем сдвига битов. Слева у вас есть знак (1 бит), смещенная экспонента (8 бит) и мантисса (23 бита).

OldBoy 25.04.2024 18:44

Изменение двух экземпляров char на unsigned char устранило проблему. Спасибо. Является ли это вообще хорошим способом «проверить» байты произвольных структур и типов? Кроме того, есть ли способ сделать это, используя беззнаковый char[], а не указатель? (Или это глупая идея?)

OutsideLoop 25.04.2024 18:48

В вашей реализации C char — это 8-битный целочисленный тип со знаком, поэтому любые значения байтов в диапазоне от 0x80 до 0xff будут обрабатываться как маленькие отрицательные целые числа в диапазоне от -128 до -1. При расширении аргументов по умолчанию для переменных аргументов printf эти 8-битные значения char будут расширены по знаку до значений int (которые в вашей реализации C являются 32-битными). Спецификатор %#x printf будет обрабатывать эти значения int как unsigned int.

Ian Abbott 25.04.2024 18:53

Ах. Что объясняет его. Сейчас я смутно помню, как читал в документации по printf о преобразовании в unsigned int перед отображением. Спасибо

OutsideLoop 25.04.2024 18:54
Вот
greg spears 25.04.2024 18:58

При разыменовании указателя вы не ограничены доступом к объекту непосредственно в месте расположения указателя. Вы можете использовать арифметику указателей или индексацию массива для доступа к близлежащим объектам. Например, вместо увеличения p в цикле вы можете заменить *p в аргументе printf на *(p + i) или p[i], чтобы получить доступ к байту по смещению i от указателя. (Есть ограничения, нельзя получить доступ за пределы объекта или массива.)

Ian Abbott 25.04.2024 18:59

Спасибо, Ян Эбботт. Это именно то, что я пытался выяснить (но мне не пришлось работать в своих экспериментах)

OutsideLoop 25.04.2024 19:03

@OldBoy: «Рассматривать это как целое число» сложно, не нарушая строгого правила псевдонимов. Типы символов имеют исключение из этого правила.

Nate Eldredge 25.04.2024 19:12

Чтобы обойти строгое правило псевдонимов, нужно использовать memcpy() для копирования байтов в целое число. И в любом случае вы должны убедиться, что используете достаточно большой целочисленный тип.

Barmar 25.04.2024 19:21

@NateEldredge Я не думаю, что строгое правило псевдонимов уместно в таком фрагменте кода.

OldBoy 25.04.2024 19:34

@OldBoy: Если бы p было int *, как вы предположили в своем предыдущем комментарии, то строгое правило псевдонимов было бы нарушено, что вызвало бы неопределенное поведение.

Andreas Wenzel 25.04.2024 19:46

Обратите внимание, что при форматировании чисел размера char уместно использовать соответствующий модификатор ширины hh. В данном случае это будет %#hhx. Вы также должны сопоставить знаковость: %x, %u и %o ожидают значения беззнакового типа, тогда как %d и %i ожидают значения знакового типа. На практике иногда удается получить правильный только один из этих атрибутов, особенно для вывода, но на это не следует полагаться.

John Bollinger 25.04.2024 21:40

Это приятно знать. Есть ли хорошая шпаргалка для printf? (Я нашел несколько, но они не показались мне такими уж хорошими.) Я думал, что таблицы в стандартной документации будет достаточно, но мне не хватало таких вещей, как приведенные выше (особенно модификаторы длины).

OutsideLoop 25.04.2024 23:05

@AndreasWenzel Это не то, что я предлагал. Я предложил рассматривать переменную с плавающей запятой как целое число с помощью простого приведения, чтобы получить доступ к трем наборам битов.

OldBoy 26.04.2024 08:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
15
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку char подписывается и повышается до int при использовании в printf. Именно по этой причине вы видите эти отрицательные целые числа, напечатанные в шестнадцатеричном виде.

Используйте беззнаковый тип и правильный формат.

int main() {

    float f1 = -12.78;
    const unsigned char *p =  (const unsigned char *)&f1;
    for(int i=0; i < sizeof(f1); i++) {
        printf("i: %i and p: %p and byte is %hhx\n", i, (void *)p,*p);
        p++;
    }
}
char подписан на конкретном компиляторе и флагах ОП. В других настройках он может быть беззнаковым, поскольку char, что довольно уникально, имеет знаковость, определяемую реализацией, и отличается как от signed char, так и от unsigned char.
ShadowRanger 26.04.2024 02:10

Беззнаковый символ @ShadowRanger всегда будет работать. нет.

gulpr 26.04.2024 02:32

Я согласен, что unsigned char работает. Я говорю, что ваше первое предложение можно прочитать как подразумевающее, что char всегда подписано, хотя это не так.

ShadowRanger 26.04.2024 02:39

Другие вопросы по теме