Не удается получить точное шестнадцатеричное значение при чтении файла

Код =>

#include<stdio.h>

typedef struct {
    unsigned short c2;
    unsigned long c4;
} TAKECH;

int main() {
    TAKECH tch;
    FILE *fp_in;

    fp_in = fopen("in.txt","rb");

    fread(&tch,6,1,fp_in);

    printf("First two bytes: %x\n",tch.c2);
    printf("Next four bytes: %x\n",tch.c4);

    fclose(fp_in);

    return 0;
}

Выход =>

First two bytes: 6261
Next four bytes: bfd56665

in.txt =>

abcdef

Hexeditor (редактор vim:%!xxd) показать это =>

0000000: 6162 6364 6566 0a                        abcdef.

Требуется объяснение вывода:

First two bytes: 6261 ‹-- Почему в обратном порядке?

First two bytes: 6162 ‹-- Разве это не должно быть?

Почему я не могу получить 6364 на выходе? Как я могу получить следующие четыре байта (6364 6566) с printf("Next four bytes: %x\n",tch.c4); Почему я получаю Next four bytes: bfd56665, откуда bfd5?

Любой ответ будет высоко оценен.

Заранее спасибо.


person shibly    schedule 23.05.2016    source источник
comment
Почему этот вопрос помечен с++?   -  person Biruk Abebe    schedule 23.05.2016
comment
Что вы ожидаете от fread(&tch,6,1,fp_in);? Честно говоря, самый простой ответ заключается в том, что делать это на самом деле не имеет особого смысла.   -  person David Schwartz    schedule 23.05.2016
comment
@DavidSchwartz, я хочу назначить первые два байта tch.c2(ab) и последние четыре байта tch.c4(cdef) .   -  person shibly    schedule 23.05.2016
comment
@shily Так почему бы вам не написать код для этого ?! Создайте шестибайтовый буфер, например char buf[6];, прочитайте в него файл, а затем поместите каждый байт точно туда, куда вы хотите, например tch.c2=buf[0]; tch.c2<<=8; tch.c2|=buf[1]; или куда угодно. Вы не можете случайным образом указывать указатели и ожидать разумных результатов, вы должны кодировать то, что вы действительно хотите. (Кроме того, вы задали неправильный вопрос. Укажите, что вы хотите сделать, и попросите, как это сделать. Не просите помощи в решении проблемы таким образом, который изначально не имеет смысла!)   -  person David Schwartz    schedule 23.05.2016
comment
@DavidSchwartz, можно ли сделать то же самое, используя структуру вместо буфера?   -  person shibly    schedule 23.05.2016
comment
@Shily Возможно ли это, да. Это умно, нет. Зачем писать сложный, необоснованно непереносимый код, когда есть гораздо более простое, на 100% переносимое решение? И, в качестве бонуса, вы на самом деле пишете код, который делает именно то, что вы хотите сделать, а не используете комбинацию обстоятельств, которые вы тщательно подстраиваете, чтобы они делали то, что вы хотите, а затем ваш код сломается, если эти обстоятельства изменятся.   -  person David Schwartz    schedule 23.05.2016


Ответы (6)


Изменение структуры TAKECH следующим образом:

typedef struct {
    unsigned short c2;
    unsigned long c4;
} __attribute__((packed)) TAKECH;

Вот объяснение __attribute__((packed).

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

person PeanutStars    schedule 23.05.2016

Вы должны использовать буфер для fread (см. пример на http://en.cppreference.com/w/c/io/fread), а не struct.

Из-за заполнения вы получаете только два "правильных" байта из файла (65 и 66). Остальные байты c4 не инициализированы.

Для «проблемы» заказа вы можете взглянуть на: Почему fread мешает моему порядку байтов?


Это зависит от машины/компилятора, поэтому фактические результаты могут отличаться.

typedef struct
{
  uint16_t c2;
  uint32_t c4;
} TAKECH;

sizeof(TAKECH) равно 8 (не 6 = sizeof(c2) + sizeof(c4)): добавлено заполнение для удовлетворения ограничений выравнивания (выравнивание структуры данных влияет как на производительность, так и на правильность программ).

typedef struct
{
  uint16_t c2;  /* 2 bytes */
                /* 2 padding bytes */
  uint32_t c4;  /* 4 bytes */
} TAKECH;

(см. также Почему sizeof для структуры не равен сумме sizeof каждого члена?).

person manlio    schedule 23.05.2016
comment
Я также хотел бы упомянуть, что OP предполагает, что short имеет ширину 2 байта, а long - 4 байта, что неверно. short имеет ширину минимум 2 байта, а long имеет ширину минимум 4 байта. - person Mohamad Elghawi; 23.05.2016
comment
@manlio, я не мог понять концепцию заполнения, можете ли вы уточнить ее подробнее? Как я могу получить значение 6364? - person shibly; 23.05.2016
comment
Ах, я понял заполнение отсюда: stackoverflow.com/questions /6968468/padding-in-structures-in-c - person shibly; 23.05.2016
comment
@Shily Я добавил некоторые детали. - person manlio; 23.05.2016
comment
@manlio, я понял концепцию заполнения, как я могу получить значение 6364 - person shibly; 23.05.2016
comment
@shily Вы можете использовать два вызова fread: fread(&tch.c2, sizeof(tch.c2), 1, fp_in); и fread(&tch.c4, sizeof(tch.c4), 1, fp_in); (но вы должны настроить порядок следования байтов). - person manlio; 23.05.2016

Первые два байта: 6261 ‹-- Почему в обратном порядке?

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

Вот объяснение.

person trojanfoe    schedule 23.05.2016

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

#include <iostream>
#include <cstring>

typedef struct {
    unsigned short c2;
    unsigned long c4;
} TAKECH;

#pragma pack(push,1)
typedef struct {
    unsigned short c2;
    unsigned long c4;
} TAKECH_packed_1;
#pragma pack(pop)

const unsigned char data[] = "\x61\x62\x63\x64\x65\x66\x0a\xff\xfe\xfd\xfc";

int main() {
    TAKECH original;
    std::memcpy(&original, &data, sizeof(original));
    std::cout << std::hex;
    std::cout << "Default packing:\n";
    std::cout << "    c2: " << original.c2 << '\n';
    std::cout << "    c4: " << original.c4 << '\n';

    TAKECH_packed_1 packed;
    std::memcpy(&packed, &data, sizeof(packed));
    std::cout << "\nByte packing:\n";
    std::cout << "    c2: " << packed.c2 << '\n';
    std::cout << "    c4: " << packed.c4 << '\n';
}

Это выводит

Default packing:
    c2: 6261
    c4: ff0a6665

Byte packing:
    c2: 6261
    c4: 66656463
person Christopher Oicles    schedule 23.05.2016

Здравствуйте, могу ли я предложить вам очистить вашу структуру tch перед назначением, так как она заполнена мусором.

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

mmcmbp:scratch abe$ cat main.c 

#include <stdio.h>
#include <string.h>

typedef struct {
    unsigned short c2;
    unsigned long c4;
} TAKECH;

int main() {
    TAKECH tch;
    FILE *fp_in;

    memset(&tch, 0, sizeof(TAKECH));

    printf("Before\n");
    printf("First two bytes: %hu\n",tch.c2);
    printf("Next four bytes: %lu\n",tch.c4);

    fp_in = fopen("in.txt","rb");

    fread(&tch,6,1,fp_in);

    printf("After:\n");
    printf("First two bytes: %hu\n",tch.c2);
    printf("Next four bytes: %lu\n",tch.c4);

    fclose(fp_in);

    return 0;
}

Сборник:

mmcmbp:scratch abe$ clang main.c -o main

Исполнение:

mmcmbp:scratch abe$ ./main

Before
First two bytes: 0
Next four bytes: 0
After:
First two bytes: 25185
Next four bytes: 0

И в соответствии с порядком байтов, да, порядок байтов может диктовать это, и это то, что заявили другие.

person Abraham    schedule 23.05.2016

Ваш код будет работать, если TAKECH будет выглядеть так:

    Low address                                 High address
    |        c2       |                  c4                |
    | Byte 1 | Byte 0 | Byte 3 |  Byte 2 | Byte 1 | Byte 0 |

но на самом деле это так:

    Low address                                              High address
    |        c2       |   Padding   |                  c4               |
    | Byte 0 | Byte 1 |      |      | Byte 0 | Byte 1 | Byte 2 | Byte 3 |

tch:    61       62      63     64      65       66    junk(d5) junk(bf)

Первая проблема, порядок, связана с тем, что ваш компьютер использует little-endian — младший значащий байт многобайтового целого числа хранится по младшему адресу.

Вторая проблема связана с вашим предположением, что sizeof(TAKECH) равно шести.
Это не так; он был дополнен, чтобы сделать адрес c4 кратным sizeof(unsigned long).
Это приводит к тому, что часть tch ("верхние" два байта tch.c4) не инициализируется, когда вы читаете только шесть байтов.

Надежное и переносимое решение состоит в том, чтобы читать каждый элемент отдельно,

fread(&tch.c2, sizeof(tch.c2), 1, fp_in);
fread(&tch.c4, sizeof(tch.c4), 1, fp_in);

и отрегулируйте endianness впоследствии.

Резюме:

  • Всегда используйте sizeof вместо того, чтобы полагаться на предположения.
  • При работе с двоичными данными вы должны знать как о заполнении, так и о порядке следования байтов.
person molbdnilo    schedule 23.05.2016
comment
Итак, как я могу получить значение 6364? Он полностью потерян? - person shibly; 23.05.2016