1

I am trying to write a C program to read in a block of instrumentation data and pull out specific bytes into variables. Data is of mixed format such as bytes 4-5 are an integer, bytes 10-14 a float, etc.

I wrote an experimental program to test extracting values from a byte (char) array and I don't understand the results. My test program is running on a Raspberry Pi 3 running Debian Jessie.

Here is my program:

Convert byte array to parts

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

void main()
{

// Test Data Array
unsigned char v1[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                      0x40, 0xB1, 0x10, 0x55, 0xEE, 0x5A, 0xC7, 0x39,
                      0xBB, 0x22, 0x04, 0xB2, 0xF4, 0xF5, 0xF6, 0xF7,
                      0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFF, 0x87};

signed char        c; // 1-byte
unsigned char      u; // 1-byte
signed short       i; // 2-byte
unsigned  short    j; // 2-byte
signed int         k; // 4-byte
unsigned int       l; // 4-byte
signed long long   m; // 8-byte
unsigned long long n; // 8-byte
float              x; // 4-byte
double             y; // 8-byte
long double        z; // 8-byte

// Display type sizes
printf("\nsizeof's: %u %u %u %u %u   %u %u %u   \n\n",sizeof(char),
    sizeof(short),sizeof(int),sizeof(long),sizeof(long long),
    sizeof(float),sizeof(double),sizeof(long double));

printf("Addresses: \n");
printf("   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n   %08X\n\n",
   &c,&u,&i,&j,&k,&l,&m,&n,&x,&y,&z);

// Display Endianess
unsigned int e1 = 1;
char *e2 = (char *)&e1;
if (*e2)
    printf("Little Endian\n\n");
else
    printf("Big Endian\n\n");

// Copy bytes to assorted variables

memcpy(&i,&v1[30],2);
printf("Signed Short:   %04X %hd\n",i,i); // FF 87

memcpy(&j,&v1[30],2);
printf("Unsigned Short: %04X %hu\n",j,j); // FF 87

memcpy(&k,&v1[16],4);
printf("Signed Int:     %08X %d\n",k,k);  // BB 22 04 B2

memcpy(&l,&v1[16],4);
printf("Unsigned Int:   %08X %u\n",l,l);  // BB 22 04 B2

memcpy(&x,&v1[8],4);
printf("Float:          %08X %f\n",x,x);  // 40 B1 10 55

memcpy(&y,&v1[8],8);
printf("Double:         %16X %lf\n",y,y);  // 40 B1 10 55 EE 5A C7 39

}

The results I get:

pi@raspberrypi:~/Desktop $ ./convertIt_bytes2

sizeof's: 1 2 4 4 8 4 8 8

Addresses: 7EA7E5DB 7EA7E5DA 7EA7E5D8 7EA7E5D6 7EA7E5D0 7EA7E5CC 7EA7E5C0 7EA7E5B8 7EA7E5B4 7EA7E5A8 7EA7E5A0

Little Endian

Signed Short: FFFF87FF -30721

Unsigned Short: 87FF 34815

Signed Int: B20422BB -1308351813

Unsigned Int: B20422BB 2986615483

Float: 7EA7E5E8 9943184834560.000000

Double: 7EA7E5EC 0.000000

The type sizes used with memcpy look correct from my printout of sizeof's so why is my output only partially correct? The Signed Short hex value is printed with 8 characters instead of 4 even though it's a 2-byte word printed with %04X.

The subsequent unsigned short and int outputs are correct, but the following float and double hex values are garbage and appear to be addresses not values as the outputs are close to the variable addresses printed above. What is going on?

pirho
  • 11,565
  • 12
  • 43
  • 70
jeff_c
  • 23
  • 1
  • 4
  • They are caused by misused printf format. You need to read the compiler warning. – llllllllll Jan 29 '18 at 07:08
  • 1
    [See What should main() return in C and C++?](http://stackoverflow.com/questions/204476/) and [C11 Standard §5.1.2.2.1 Program startup (draft n1570)](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf). You will also want to note, using improper conversion specifiers in your *format string* invokes *Undefined Behavior*. You will want to use `%p` to print pointer addresses. – David C. Rankin Jan 29 '18 at 07:19

1 Answers1

1

Take the following:

memcpy(&i,&v1[30],2);
printf("Signed Short:   %04X %hd\n",i,i); // FF 87

Passing chars and shorts as parameters to variadic argument functions such as printf will result in those parameters getting implicitly cast to integers.

i is "short" (16-bit). And as it is passed to printf, if gets converted to int (32-bit) before hitting the stack. And that includes the sign extension. 0xff87 is a negative number (as a short). Hence 0xff87 gets cast to 0xffffff87 before printing.

As a learning exercise, try this:

printf("Signed Short:   %04X %hd\n",(unsigned short)i,(unsigned short) i); // FF 87

That will force coercision from short to unsigned short to integer. Which is different than going from short to integer.

Update: Since you asked about "printing a double value in hex form". You basically need to interpret the address holding that value at as an array of hexadecimal bytes. For example:

double d = 3.14159;
unsigned char* ptr = (unsigned char*)&d;
for (size_t i = 0; i < sizeof(double); i++)
    printf("%02x ", ptr[i]);

prints:

6e 86 1b f0 f9 21 09 40

Byte order might be different than you expected because I'm running on x86 Intel. Little Endian (or Intel's own specific layout) might influence the in-memory byte order for floating point.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • I tried your suggestion above and the printf output is still: – jeff_c Jan 30 '18 at 03:28
  • I tried your suggestion above only using "(unsigned short)i,(signed short)i" as I declared i to be signed and it worked. Thanks. However float and double values are still garbage after trying to cast values to int and long long to output as hex. – jeff_c Jan 30 '18 at 03:42
  • `printf` is dumb, annoying, and potentially dangerous. The format specifiers in the format string (e.g. `%d %f %s`) are hints as to what has been pushed to the stack with the subsequent arguments. So if you push a double (typically 8-bytes) and the corresponding format specifier is `%d` (interpreted as a 4-byte integer), then the wrong number of bytes is going to get popped off the stack. That whole thing about forced coercion only applies to integer types (short and char). Everything else (pointers, strings, doubles, floats) need to match the format specifier precisely. – selbie Jan 30 '18 at 04:08
  • Thanks for clueing me in on printf's "features". Searching on stackoverflow for how to print floats and doubles as hex the best solution seems to be to memcpy to an integer then print the integer with the hex format "X". That worked and the output I get is as expected. – jeff_c Jan 31 '18 at 04:48
  • I added an example for printing a `double` in `hex` above. You don't actually need to memcpy. You can just take the address of the double and cast to a pointer (array). – selbie Jan 31 '18 at 17:06