Как разделить Int на два байта в C?

Я работаю с программным обеспечением, встроенным в минимальное оборудование, которое поддерживает только ANSI C и имеет минимальные версии стандартных библиотек ввода-вывода.

У меня есть переменная Int размером два байта, но мне нужно разделить ее на 2 байта отдельно, чтобы иметь возможность передать, а затем я могу, прочитав два байта, собрать исходный Int.

Я могу представить себе двоичное деление каждого байта следующим образом:

int valor = 522;  // 0000 0010 0000 1010 (entero de 2 bytes)
byte superior = byteSuperior(valor);  // 0000 0010
byte inferior = byteInferioror(valor);  // 0000 1010
...
int valorRestaurado = bytesToInteger(superior, inferior); // 522

но мне не удается простым способом разделить целое по его весу, и это дает мне ощущение, что это должно быть тривиально (например, со смещением битов), и я этого не обнаруживаю.

На самом деле, любое решение, которое делит целое на 2 байта и собирает его заново, служит мне хорошо.

От уже большое спасибо!

Вы отправляете данные из одной системы в другую, у которой другой порядок байтов? Если это так, вы можете использовать htons() и ntohs(), если они доступны в ваших системах. Вы используете htons(), чтобы преобразовать двухбайтовое значение int в сетевой порядок байтов, затем, получив его, вы используете ntohs(), чтобы преобразовать его обратно в порядок байтов хоста для хоста, на котором вы его получили.

Andrew Henle 27.07.2018 12:34

Пусть вас не пугают длинные и сложные ответы. В основном это простая проблема, и легко написать достойный код, который будет работать на вашем компьютере в 100% случаев. Выберите один из ответов с участием >> и & 0xff, внимательно проверьте его, как предлагает мой ответ, и все будет в порядке.

Steve Summit 27.07.2018 15:04

@SteveSummit с акцентом на "на твоей машине";) Да, удивительно то, что звучит так просто, это такая сложная проблема в C. Я думаю, что все эти ответы есть, вы должны знать об этих вещах, даже если только для выбор метода, определяемого реализацией, наиболее подходящего для вашей (единственной) целевой системы :)

user2371524 27.07.2018 17:27

@FelixPalmen Раньше я был одним из самых одержимых портативностью программистов, которых я знал, но я думаю, что к старости я становлюсь слабым. И, конечно же, есть веские причины, по которым подписанные и неподписанные одинаковые размеры имеют тенденцию тихо и правильно преобразовываться друг в друга на всех популярных процессорах (и почему подписанное целочисленное переполнение имеет тенденцию повторяться так же предсказуемо, как и unsigned).

Steve Summit 27.07.2018 17:38
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
4
7 122
7

Ответы 7

Фактически вы можете преобразовать адрес целочисленной переменной в указатель на символ (unsigned char*, если быть точным), прочитать значение и затем увеличить указатель, чтобы он указывал на следующий байт, чтобы снова прочитать значение. Это соответствует правилам псевдонима.

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

int valor = 522;
unsigned char superior = (valor >> 8) & 0xff;
unsigned char inferior = valor & 0xff;

int valorRestaurado = (superior << 8) | inferior;

Хотя это выглядит просто, при написании такого кода всегда есть несколько тонкостей, и легко ошибиться. Например, поскольку valor подписан, его сдвиг вправо с помощью >> определяется реализацией, хотя обычно это означает, что он может подписывать расширение или нет, что в конечном итоге не повлияет на значение байта, который & 0xff выбирает и назначает superior.

Кроме того, если superior или inferior определен как подписанный тип, во время реконструкции могут возникнуть проблемы. Если они меньше, чем int (как, конечно, обязательно), они будут немедленно расширены до int до того, как произойдет остальная реконструкция, уничтожив результат. (Вот почему я явно объявил superior и inferior как тип unsigned char в моем примере. Если ваш тип byte является typedef для unsigned char, это тоже будет хорошо.)

Также существует неясная возможность переполнения, скрывающаяся в подвыражении superior << 8, даже если superior беззнаковый, хотя на практике это вряд ли вызовет проблему. (Дополнительное объяснение см. В комментариях Эрика Постпишила.)

Значение valor >> 8 определяется реализацией, когда значение valor отрицательное. Хотя в показанном примере valor является положительным, этот код не предназначен для общего использования. Кроме того, учитывая 16-битный int, superior << 8 может переполниться, и в этом случае поведение не определено стандартом C.

Eric Postpischil 27.07.2018 13:01

@EricPostpischil Я был бы признателен за объяснение того, как superior << 8 может переполняться.

Steve Summit 27.07.2018 15:47

В вопросе указано, что int - это два байта. Предположим, 8-битные байты, максимальное значение int равно 32767. В коде в этом ответе superior - это unsigned char. Согласно C 2011 (N1570) 6.5.7 3 целочисленные рекламные акции выполняются для операндов <<. Согласно 6.3.1.1 2, целочисленные рекламные акции продвигают unsigned char в int. Значение superior может находиться в диапазоне от 0 до 255. Предположим, это 128 (или любое значение от 128 до 255). Согласно 6.5.7 4, если 128 × 2 ^ 8 не представляется в int, поведение 128 << 8 не определено. Поскольку 128 × 2 ^ 8 равно 32768, он не может быть представлен в int.

Eric Postpischil 27.07.2018 16:30

Кроме того, согласно 6.5.7 3, тип результата << - это тип продвинутого левого операнда, то есть это int. Таким образом, superior << 8 пытается сдвинуть unsigned char в старшие биты int. Если установлен старший бит unsigned char, это превышает значение int. Сдвиг влево значения со знаком математически определяется стандартом C, а не как битовая операция, поэтому он переполняется, а не определяется для установки бита знака.

Eric Postpischil 27.07.2018 16:33

@EricPostpischil А, верно. Спасибо. Возможно, мне следовало принять близко к сердцу слова «математический подход» в другом моем ответе и вместо этого пойти по дороге superior * 256 + inferior. (Разумеется, с перевернутым мышлением, и superior вынужден явно указать тип подписано.)

Steve Summit 27.07.2018 16:49
superior * 256 тоже переливается. Необходимо выполнить арифметику в более широком типе, или обусловить ее использование разных выражений для разных значений, или реализовать какой-то другой обходной путь.
Eric Postpischil 27.07.2018 16:56

@EricPostpischil Хм. Я не думал, что superior * 256 может переполниться, но я не думал, что superior << 8 может, поэтому держу пари, что вы собираетесь рассказать мне, как может происходить знаковое умножение. :-)

Steve Summit 27.07.2018 17:43

@SteveSummit с 16-битным int, может, если superior больше 127.

user2371524 27.07.2018 17:58

@FelixPalmen Конечно. Но если superior - это подписано и 8 бит (как в этом подпотоке обсуждения), конечно, он не может быть больше 127.

Steve Summit 27.07.2018 18:00

@SteveSummit: В коде этого вопроса superior - это unsigned char. Когда применяются целочисленные рекламные акции, он становится int, но значение не изменяется. Таким образом, 128 или 255 в superior превратятся в int со значением 128 или 255. Умножение этого на 256 в реализации с 16-битным int приводит к переполнению.

Eric Postpischil 27.07.2018 19:34

@EricPostpischil В комментарии, предлагающем умножение на 256, я также указал (в скобках), что в этом случае нам придется вернуться к использованию явно подписанных значений для таких вещей, как superior, а не без знака.

Steve Summit 27.07.2018 20:44

Это непростая задача.

Прежде всего, тип данных для байт в C - char. Вы, вероятно, захотите здесь unsigned char, поскольку char может быть подписанным или неподписанным, это определяется реализацией.

int - это тип со знаком, поэтому сдвиг вправо также определяется реализацией. Что касается C, int должен иметь 16 бит по меньшей мере (что было бы 2 байта, если char имеет 8 бит), но может иметь больше. Но поскольку ваш вопрос написан, вы уже знаете, что int на вашей платформе имеет 16 бит. Использование этих знаний в вашей реализации означает, что ваш код специфичен для этой платформы и не является переносимым.

На мой взгляд, у вас есть два варианта:

  1. Вы можете работать со значением вашего int, используя маскировку и битовый сдвиг, например:

    int foo = 42;
    unsigned char lsb = (unsigned)foo & 0xff; // mask the lower 8 bits
    unsigned char msb = (unsigned)foo >> 8;   // shift the higher 8 bits
    

    Это имеет то преимущество, что вы не зависите от расположения вашего int в памяти. Для реконструкции сделайте что-нибудь вроде:

    int rec = (int)(((unsigned)msb << 8) | lsb );
    

    Обратите внимание, что преобразование msb в unsigned здесь необходимо, иначе он будет преобразован в int (int может представлять все значения unsigned char), что может привести к переполнению при сдвиге на 8 позиций. Как вы уже сказали, ваш int имеет «два байта», в вашем случае это очень вероятно.

    Последним преобразованием в int также является определяется реализацией, но он будет работать на вашей «типичной» платформе с 16-битным int в дополнении 2, если компилятор не сделает что-то «странное». Сначала проверив, не слишком ли велик unsigned для int (потому что исходный int был отрицательным), вы могли бы избежать этого, например.

    unsigned tmp = ((unsigned)msb << 8 ) | lsb;
    int rec;
    if (tmp > INT_MAX)
    {
        tmp = ~tmp + 1; // 2's complement
        if (tmp > INT_MAX)
        {
            // only possible when implementation uses 2's complement
            // representation, and then only for INT_MIN
            rec = INT_MIN;
        }
        else
        {
            rec = tmp;
            rec = -rec;
        }
    }
    else
    {
        rec = tmp;
    }
    

    Дополнение 2 здесь подходит, потому что правила преобразования отрицательного int в unsigned явно указаны в стандарте C.

  2. Вы можете использовать представление в памяти, например:

    int foo = 42;
    unsigned char *rep = (unsigned char *)&foo;
    unsigned char first = rep[0];
    unsigned char second = rep[1];
    

    Но будьте осторожны, будет ли first MSB или LSB, зависит от порядок байтов, используемого на вашем компьютере. Кроме того, если вашего int содержит биты заполнения (крайне маловероятно на практике, но разрешено стандартом C), вы также их прочтете. Для реконструкции сделайте что-нибудь вроде:

    int rec;
    unsigned char *recrep = (unsigned char *)&rec;
    recrep[0] = first;
    recrep[1] = second;
    

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

user2371524 27.07.2018 13:17

@EricPostpischil уверен, что это так, он даже сделал перед, вы написали свой первый комментарий. В любом случае, «частичный» ответ все равно не даст повода для отрицательных голосов.

user2371524 27.07.2018 13:25

Извините, не знаю, как пропустил реконструкцию. Однако реконструкция в части 1 неверна, поскольку преобразование из unsigned в int определяется реализацией, когда значение не может быть представлено в int.

Eric Postpischil 27.07.2018 13:28

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

Eric Postpischil 27.07.2018 13:30

@EricPostpischil это можно обойти, но не при работе с битами значения. И я четко упомянул "реализацию, определенную" во введении, я не вижу причин разбрасывать ее повсюду.

user2371524 27.07.2018 13:31

Во введении говорится, что подписанность char определяется реализацией. Это не утверждение, что код в ответе полагается на поведение, определяемое реализацией при преобразовании из unsigned в int.

Eric Postpischil 27.07.2018 13:33

@EricPostpischil во введении больше говорится о непереносимом коде. И такое поведение действительно мешает людям больше писать ответы. В любом случае, я даже добавил сюда полное объяснение.

user2371524 27.07.2018 14:08
rec = ~tmp + 1; не может быть представлен в int, если tmp представляет собой INT_MIN. Пример работающей конвертации - int foo(unsigned x) { if (x <= INT_MAX) return x; else if (x == INT_MIN) return INT_MIN; else return - (int) -x; }. Apple LLVM 9.1.0 (clang-902.0.39.2) полностью оптимизирует это. Он даже полностью оптимизирует int foo(unsigned x) { if (x <= INT_MAX) return x; else { int y = 0; while (x++) --y; return y; } }, хотя я бы не рекомендовал этого, поскольку другие компиляторы могут этого не делать.
Eric Postpischil 27.07.2018 14:19

Мой голос против (который я удалил) был связан с тем, что в этом ответе был указан код который сломается при некоторых обстоятельствах. Вы могли бы избежать этого голосования, просто указав поведение, определяемое реализацией, как предварительное условие. Вы комментируете «такое поведение», но я рад препятствовать получению плохих ответов, проголосовав за них и объяснив, почему. Нет необходимости защищать свои драгоценные ответы как личное оскорбление. Относитесь к нему как к неодушевленному объекту, к которому у вас нет никакой личной связи. Если у него есть дефекты, значит, у него есть дефекты, и нужно исправить их, а не спорить о них.

Eric Postpischil 27.07.2018 14:23

У него не было "дефекты", и я, делать, считаю, что это личное голосование (с некоторыми усилиями), особенно, когда даже не оставляю комментарий (что не относится к ваш downvote). И что касается вашего другого комментария, в моем коде tmp не может быть INT_MIN в этой строке.

user2371524 27.07.2018 17:12

Ну, я понимаю, вы имеете в виду "эквивалент" INT_MIN ... это действительно проблема, странный угловой случай, но исправляется простой дополнительной строкой.

user2371524 27.07.2018 17:21

Под tmp как INT_MIN я имел в виду, что он имеет значение, соответствующее INT_MIN, в частности (unsigned) INT_MIN, которое в реализации OP равно 32768. Если msb равен 128, а lsb равен 0, то tmp установлен на 32768, ~tmp равен 32767, ~tmp + 1 равен 32768, а rec = ~tmp + 1; переполняется, потому что int не может представлять 32768.

Eric Postpischil 27.07.2018 17:24

@EricPostpischil уже понял (см. Предыдущий комментарий) и исправил, выполнив дополнение 2 в unsigned.

user2371524 27.07.2018 17:25

В новом коде: Снова tmp, созданный из msb, и lsb может быть 32768. Затем tmp = ~tmp + 1; устанавливает tmp на 32768, и rec = tmp; пытается присвоить int значение, которое int не может представлять. В этом случае выполняется преобразование, и поведение определяется реализацией, а не неопределенным, согласно 6.3.1.3 3. OP не указал, что определяет реализация, поэтому мы не можем знать, будет ли этот код работать или нет.

Eric Postpischil 27.07.2018 17:27

Я устал. Конечно, это ничего не меняет. Вот почему в моем первом примере я решил довериться «нормальной» реализации, основанной на двух дополнениях, все остальное (без доступа к представлению) просто странно и сложно. Помимо оптимизаторов, должен быть способ избежать еще одного особого случая? : o в любом случае я напишу один.

user2371524 27.07.2018 17:30

Как Эрик прокомментировал в другом месте и в своем ответе: вы можете выполнять все сдвиги и соединения без знака и memcpy в / из int по краям. Лично я считаю, что это граничит с безумием, жертвуя реальной практической эффективностью ради теоретической переносимости на машины, которых, возможно, даже не существует. Но каждому свое. :-)

Steve Summit 27.07.2018 17:47

@SteveSummit во всем этом безумии, я бы назвал memcpy() в / из unsigned чем-то вроде элегантный ... но, конечно, это тоже имеет доступ к представлению :)

user2371524 27.07.2018 17:51

Я использую int shrot вместо int для dry, потому что на ПК это 4 байта, а на моей целевой платформе - 2. Используйте unsigned, чтобы упростить отладку.

Код компилируется с помощью GCC (и должен делать это практически с любым другим компилятором C). Если я не ошибаюсь, это зависит от архитектуры big endian или little endian, но это можно решить путем инвертирования строки, восстанавливающей целое число:

#include <stdio.h>

    void main(){
    // unsigned short int = 2 bytes in a 32 bit pc
    unsigned short int valor;
    unsigned short int reassembled;
    unsigned char data0 = 0;
    unsigned char data1 = 0;

    printf("An integer is %d bytes\n", sizeof(valor));

    printf("Enter a number: \n");
    scanf("%d",&valor);
    // Decomposes the int in 2 bytes
    data0 = (char) 0x00FF & valor;
    data1 = (char) 0x00FF & (valor >> 8);
   // Just a bit of 'feedback'
    printf("Integer: %d \n", valor);
    printf("Hexa: %X \n", valor);
    printf("Byte 0: %d - %X \n", data0, data0);
    printf("Byte 1: %d - %X \n", data1, data1);
    // Reassembles the int from 2 bytes
    reassembled = (unsigned short int) (data1 << 8 | data0);
    // Show the rebuilt number
    printf("Reassembled Integer: %d \n", reassembled);
    printf("Reassembled Hexa: %X \n", reassembled);
    return;
}

OP запрашивает код для разделения байтов подписанного int, но этот ответ показывает код для unsigned short и не объясняет, как его использовать для int или short. Поскольку адаптация кода для подписанных типов подвержена ошибкам из-за семантики C в отношении подписанных типов, битовых сдвигов и переполнений, это является проблемой. Например, data1 << 8 будет переполняться в реализациях C 16-битными типами int, если установлен старший бит data1. Это связано с тем, что unsigned chardata1 будет повышен до int, поэтому сдвиг будет выполняться в подписанном типе int.

Eric Postpischil 27.07.2018 13:07

OP запрашивает решение, которое собирает байты, что я и сделал

Mochuelo 27.07.2018 13:12

Проблема не в том, что этот код не предоставляет решения для повторной сборки байтов, что он не предоставляет решение для повторной сборки байтов двухбайтового int, как того требует проблема. Предоставление кода для unsigned short при запросе int не является решением.

Eric Postpischil 27.07.2018 13:22

Я не вижу, чтобы он явно запрашивал int, как вы можете прочитать в OP: «На самом деле, любое решение, которое делит целое на 2 байта и повторно собирает его, мне хорошо служит». Я думаю, не имеет значения, если это int или short, если честно

Mochuelo 27.07.2018 13:30

«У меня есть переменная Int размером два байта, но мне нужно разделить ее на 2 байта по отдельности, чтобы иметь возможность передать ее, а затем я могу, прочитав два байта, собрать исходный Int». [Курсив добавлен.]

Eric Postpischil 27.07.2018 13:31

Я повторю это очередной раз только для вас: «На самом деле, любое решение, которое делит целое на 2 байта и собирает его заново, служит мне хорошо». [Курсив добавлен.]

Mochuelo 27.07.2018 14:33

Я повторю это очередной раз только для вас: вопрос требует решения для int.

Eric Postpischil 27.07.2018 14:51

Это код неправильный, потому что он выходит за пределы целевой реализации OP: data1 << 8.

Eric Postpischil 27.07.2018 14:52

вы совершенно неправильно, я только что протестировал его, и он работает на моей машине.

Mochuelo 27.07.2018 14:57

Ребята. Мир. Вы не собираетесь убеждать друг друга. Один из вас говорит о коде, который на сегодня достаточно хорош; один из вас говорит о коде, который гарантированно будет работать на любой машине сейчас или до скончания веков. Оба ответа имеют свое место; ни то, ни другое не является полностью правильным или полностью неправильным.

Steve Summit 27.07.2018 15:07

Тот факт, что он «работает» на вашем компьютере, ни в коем случае не свидетельствует о том, что он работает на реализации OP. Правила для C устанавливаются стандартом C, а не тем, как работает ваша машина, и OP конкретно запрашивает решение для своей машины, а не для вашей.

Eric Postpischil 27.07.2018 16:19

Просто определите союз:

typedef union
{
   int           as_int;
   unsigned char as_byte[2];
} INT2BYTE;

INT2BYTE i2b;

Поместите целочисленное значение в член i2b.as_int и получите байтовый эквивалент из i2b.as_byte[0] и i2b.as_byte[1].

который имеет те же последствия (например, порядок байтов, биты заполнения), что и "вручную" псевдоним с unsigned char

user2371524 27.07.2018 13:24

@FelixPalmen Кто сказал, что оба конца имеют разный порядок байтов?

i486 27.07.2018 13:48

Хорошо, я? Кто сказал, что эта платформа не имеет биты заполнения? Я просто думаю, что следует добавить несколько слов предостережения, рекомендуя изучить представление.

user2371524 27.07.2018 13:52

Мое решение - простой и прямой ответ на вопрос «Как разделить Int на два байта в C». Никакой высокой переносимости или других претензий. Судя по стилю определения вопроса, я предполагаю, что ОП в этом нуждается.

i486 27.07.2018 14:58

Что очень плохо. Основываясь на приведенных здесь ответах, я бы не стал винить OP за то, что он испугался подхода сдвига и маски и вместо этого принял подход на основе char * или union. Но в реальном мире я считаю, что методы, основанные на char * и union, с большей вероятностью будут иметь реальные ошибки или проблемы с переносимостью, чем прилично написанный код сдвига и маски.

Steve Summit 27.07.2018 17:13

Метод @SteveSummit union - это «другой» метод. Когда многие ответы объясняют метод сдвига, что я могу сделать - предложить другой метод или снова повторить классическое преобразование сдвига? И это массив char, а не char *.

i486 27.07.2018 20:32

@ i486 Не волнуйтесь, я не говорил, что с вашим ответом что-то не так. Когда я сказал «очень плохо», я сетовал на поворот этого вопроса и всех его ответов, создавая впечатление, что техники сдвига и маски страшны и опасны, и их следует избегать. Я не верю, что это так, но чем больше слов о них пишут здесь, тем страшнее они кажутся, поэтому я постараюсь прекратить писать сейчас. :-) (P.S. Нет, вы не использовали char *, но использование указателей на символы - это еще один способ получить байты типа int.)

Steve Summit 27.07.2018 20:51

Как видно из нескольких ответов, есть несколько подходов и некоторые, возможно, удивительные тонкости.

  1. «Математический» подход. Вы разделяете байты, используя сдвиг и маскирование (или, что то же самое, деление и остаток), и аналогичным образом рекомбинируете их. Это «вариант 1» в Ответ Феликса Палмена. Преимущество этого подхода в том, что он полностью не зависит от проблем с порядком следования байтов. У него есть сложность, заключающаяся в том, что он подвержен некоторым проблемам с расширением знаков и определенностью реализации. Безопаснее всего использовать тип unsigned как для составного int, так и для частей уравнения, разделенных байтами. Если вы используете подписанные типы, вам обычно потребуются дополнительные приведения и / или маски. (Но с учетом сказанного, я предпочитаю этот подход.)

  2. "Память" подход. Вы используете указатели или union для прямого доступа к байтам, составляющим int. Это «вариант 2» в ответе Феликса Палмена. Очень важной проблемой здесь является порядок байтов, или "порядок байтов". Кроме того, в зависимости от того, как вы его реализуете, вы можете столкнуться с правило "строгого алиасинга".

Если вы используете «математический» подход, сделайте Конечно, вы протестируете его на значениях, которые имеют и не имеют старшего бита различных установленных байтов. Например, для 16 бит полный набор тестов может включать значения 0x0101, 0x0180, 0x8001 и 0x8080. Если вы напишете код неправильно (если вы реализуете его с использованием подписанных типов или если вы опустите некоторые из необходимых в противном случае масок), вы обычно обнаружите, что дополнительные 0xff вкрадываются в восстановленный результат, искажая передачу. (Кроме того, вы можете подумать о написании формального модульный тест, чтобы вы могли максимизировать вероятность того, что код будет повторно протестирован и обнаружены любые скрытые ошибки, если / когда он перенесен на машину, которая делает различные варианты реализации, которые повлиять на это.)

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

Возвращаясь к предложенным мной тестовым значениям (0x0101, 0x0180, 0x8001 и 0x8080), если вы передаете целые числа без знака, они соответствуют 257, 384, 32769 и 32896. Если вы передаете целые числа со знаком, они соответствуют 257, 384, -32767 и -32640. И если на другом конце вы получите значения вроде -693 или 65281 (которые соответствуют шестнадцатеричному 0xff01), или если вы получите 32896, когда вы ожидали -32640, это означает, что вам нужно вернуться и быть более осторожными со своими подписанными / неподписанными использование, с вашей маскировкой и / или с вашим явным расширением знака.

Наконец, если вы используете подход «памяти» и если ваш код отправки и получения выполняется на машинах с разным порядком байтов, вы обнаружите, что байты поменялись местами. 0x0102 превратится в 0x0201. Есть разные способы решить эту проблему, но это может доставлять неудобства. (Вот почему, как я уже сказал, я обычно предпочитаю «математический» подход, чтобы просто обойти проблему порядка байтов.)

Конечно, есть и гибридный подход. memcpy в unsigned, разделите биты, отправьте их. Для приема соберите unsigned из битов, затем memcpy в int.

Eric Postpischil 27.07.2018 16:20

Учитывая, что int состоит из двух байтов, а количество битов на байт (CHAR_BIT) равно восьми и используется дополнение до двух, int с именем valor может быть разобран в независимом от порядка порядке следования байтов с помощью:

unsigned x;
memcpy(&x, &valor, sizeof x);
unsigned char Byte0 = x & 0xff;
unsigned char Byte1 = x >> 8;

и может быть повторно собран из unsigned char Byte0 и unsigned char Byte1 с помощью:

unsigned x;
x = (unsigned) Byte1 << 8 | Byte0;
memcpy(&valor, &x, sizeof valor);

Заметки:

  • int и unsigned имеют одинаковый размер и выравнивание согласно C 2011 (N1570) 6.2.5 6.
  • В этой реализации нет битов заполнения для unsigned, поскольку C требует, чтобы значение UINT_MAX было не менее 65535, поэтому для представления значения необходимы все 16 битов.
  • int и unsigned имеют одинаковую последовательность байтов согласно 6.2.6.2 2.
  • Если реализация не является дополнением двух, значения, повторно собранные в одной реализации, восстановят исходные значения, но отрицательные значения не будут взаимодействовать с реализациями, использующими другую семантику знаковых битов.

Возможно, более последовательным было бы использование & 0xff при вычислении как Byte0, так и Byte1, или ни одного из них.

Steve Summit 27.07.2018 18:44

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