Advanced pointer typecasting

R

Robert Street

Hi!

I'm rather new at c++ and I'm totally confused with this kind of
typecasting:

typedef signed char int8_t;
typedef signed short int16_t;

typedef struct broadcast_hdr
{
broadcast_type_t broadcast_type;
uint16_t items_n;
uint16_t size_n;
} broadcast_hdr_t;

typedef struct broadcast_type
{
char ac;
char bc;
uint16_t number_n;
} broadcast_type_t;

typedef struct ob_levels_price
{
uint16_t maskA_n;
uint16_t maskB_n;
uint8_t levels_c;
uint8_t items_c;
char filler_2_s [2];
} ob_levels_price_t;

void HandleBroadcast(int8_t* broadcast, uint16_t length)
{
static broadcast_type_t typeA = {'B','I',9};
static broadcast_type_t typeB = {'B','D',6};
static broadcast_type_t typeC = {'B','O',14};

broadcast_type_t* bc;

bc = (broadcast_type_t*)broadcast;

if (memcmp(bc, &typeC, sizeof(broadcast_type_t)) == 0)
{
SpecificBroadcast(broadcast, length);
return;
}

}

void SpecificBroadcast(int8_t* broadcast, uint16_t length)
{

ob_levels_price_t* obPriceVolumes = (ob_levels_price_t*)broadcast;

broadcast_hdr_t* bc = (broadcast_hdr_t*)broadcast;
uint32_t substart = sizeof(*bc);
uint8_t *pBc = (uint8_t*)bc;
}

Can someone explain to me SpecificBroadcast. I don't understand how
the contents of an signed char (int8_t) can be converted to a struct
(ob_levels_price_t, broadcast_hdr_t) through its address. What could
possibly be the contents of the broadcast variable???

Really stumped.

Thanks in advance.
 
J

Jack Klein

Hi!

I'm rather new at c++ and I'm totally confused with this kind of
typecasting:

You should be, it almost certainly indicates bad design. It depends
on one guaranteed feature of the language, and a lot of things that
are not guaranteed.

One guarantee that the language makes is that a pointer to any type of
object can be converted, with a suitable cast, to a pointer to one of
the character types or to a pointer to void. If such a pointer
originally points to a valid object of its type, the char or void
pointer can later be cast back to a pointer of the original object
type and it will still point to the same object.

There is no such guarantee for any other type of pointer, as other
types of headers may use fewer bits of information than pointer to
char or void.

There is most certainly no guarantee that just any arbitrary pointer
to char or void can be cast to a pointer to another object type and be
valid. It might not have the proper alignment for the other object
type, and cause undefined behavior when used to access that type.
typedef signed char int8_t;
typedef signed short int16_t;

typedef struct broadcast_hdr
{
broadcast_type_t broadcast_type;
uint16_t items_n;
uint16_t size_n;
} broadcast_hdr_t;

typedef struct broadcast_type
{
char ac;
char bc;
uint16_t number_n;
} broadcast_type_t;

typedef struct ob_levels_price
{
uint16_t maskA_n;
uint16_t maskB_n;
uint8_t levels_c;
uint8_t items_c;
char filler_2_s [2];
} ob_levels_price_t;

void HandleBroadcast(int8_t* broadcast, uint16_t length)
{
static broadcast_type_t typeA = {'B','I',9};
static broadcast_type_t typeB = {'B','D',6};
static broadcast_type_t typeC = {'B','O',14};

broadcast_type_t* bc;

bc = (broadcast_type_t*)broadcast;

if (memcmp(bc, &typeC, sizeof(broadcast_type_t)) == 0)

The line above is very problematic. Even if "broadcast" was obtained
by casting the address a broadcast_type_t to pointer to signed char,
so there are no alignment issues, and even if the original
broadcast_type_t contained {'B', 'O', 14 }, there is no requirement
that the two structures compare equal when passed to memcmp().

Padding is allowed in structures everywhere except before the first
member. There can be padding in between members, and after the last
member. The value of the padding bytes can be different in two
structures whose actual members are identical, causing the memcmp() to
fail.
{
SpecificBroadcast(broadcast, length);
return;
}

}

void SpecificBroadcast(int8_t* broadcast, uint16_t length)
{

ob_levels_price_t* obPriceVolumes = (ob_levels_price_t*)broadcast;

broadcast_hdr_t* bc = (broadcast_hdr_t*)broadcast;
uint32_t substart = sizeof(*bc);
uint8_t *pBc = (uint8_t*)bc;
}

Can someone explain to me SpecificBroadcast. I don't understand how
the contents of an signed char (int8_t) can be converted to a struct
(ob_levels_price_t, broadcast_hdr_t) through its address. What could
possibly be the contents of the broadcast variable???

Really stumped.

Thanks in advance.

This code uses C casts, although a C++ static_cast could do the same
job.

As I said above, a pointer to void or one of the character types can
hold a pointer to any type of object. Unless the pointer originated
as a pointer to that type of object, you get undefined behavior if you
try to access an object of that type through it.

There is nothing particularly C++ about this code, it is completely
compatible with C. But it is a bad design in either language. There
are much more sensible, and portable, ways to do whatever this code
does, such as putting the structure types in a union and passing a
pointer to the union.

The code also depends on specifically non-portable behavior, the fact
that on some implementation there will be no padding in the
broadcast_type_t structure so that two structures with
member-by-member equality will compare equal when passed to memcmp().
There is no such guarantee in either C or C++, and there are compilers
where the memcmp() might fail a good part of the time even if all the
individual members of the two structures compare equal.

This code is bad, and should not be emulated.
 
N

Nick Hounsome

Robert Street said:
Hi!

I'm rather new at c++ and I'm totally confused with this kind of
typecasting:

typedef signed char int8_t;
typedef signed short int16_t;
snip

void SpecificBroadcast(int8_t* broadcast, uint16_t length)
{

ob_levels_price_t* obPriceVolumes = (ob_levels_price_t*)broadcast;

broadcast_hdr_t* bc = (broadcast_hdr_t*)broadcast;
uint32_t substart = sizeof(*bc);
uint8_t *pBc = (uint8_t*)bc;
}

Can someone explain to me SpecificBroadcast. I don't understand how
the contents of an signed char (int8_t) can be converted to a struct
(ob_levels_price_t, broadcast_hdr_t) through its address. What could
possibly be the contents of the broadcast variable???

This is what people always used to do for low level i/o stuff before void*.
Although they usually use a plain old char*

Some people still do this because when you are doing low level stuff you
often need to do byte address calculations
and that requires a pointer to something of size 1. e.g.

// read a message from a socket
void readn(int fd,void* buf,int size)
{
do
{
int nread = read(fd,buf,size); // read should take void*
size -= nread;
buf = (char*)buf + nread; // cast required purely to do
arithmetic
} while(size);
}

Even then it is best to make the function parameter void* because although
making it 'byte'* would make the function simpler
it would often force the caller to cast to something it is not.
eg.
ob_levels_price_t x;
SpecificBroadcast(&x,sizeof(x)); // will not coompile despite the fact
that it clearly should according to SpecificBroadcast
SpecificBroadcast((uint8_t*)&x,sizeof(x)); // will compile - using
reinterpret_cast only makes it uglier

Another good reason to use void* rather than some sort of char* is this is
that ostream<< will do the wrong thing and debuggers will spew garbage when
doing stack dumps or showing local vars.

IMHO It would be a nice idea to allow pointer arithmetic on void* as if it
was a pointer to 'byte' - I don't think it would
do any harm in real programs.
 
R

Robert Street

Nick Hounsome said:
This is what people always used to do for low level i/o stuff before void*.
Although they usually use a plain old char*

Some people still do this because when you are doing low level stuff you
often need to do byte address calculations
and that requires a pointer to something of size 1. e.g.

// read a message from a socket
void readn(int fd,void* buf,int size)
{
do
{
int nread = read(fd,buf,size); // read should take void*
size -= nread;
buf = (char*)buf + nread; // cast required purely to do
arithmetic
} while(size);
}

Even then it is best to make the function parameter void* because although
making it 'byte'* would make the function simpler
it would often force the caller to cast to something it is not.
eg.
ob_levels_price_t x;
SpecificBroadcast(&x,sizeof(x)); // will not coompile despite the fact
that it clearly should according to SpecificBroadcast
SpecificBroadcast((uint8_t*)&x,sizeof(x)); // will compile - using
reinterpret_cast only makes it uglier

Thanks for the help.

One more question, why can the code cast the uint8_t pointer to *both*
ob_levels_price_t* and broadcast_header_t*? Shouldn't the original
address be simply pointing to one type of struct? Any ideas?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,570
Members
45,045
Latest member
DRCM

Latest Threads

Top