Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Union of bitfields and larger type on ARM

81 views
Skip to first unread message

bitrex

unread,
Feb 23, 2016, 2:18:19 PM2/23/16
to
Does anyone know if the following "naughty" (implementation dependent)
construct would work "correctly" on the ARM Cortex M4, compiled with
arm-gcc-embedded?

typedef union {
uint32_t phase_accumulator_full;
struct {
uint8_t integer_high : 8,
integer_low : 8,
frac_high : 8,
frac_low : 8;

};
} phase_accumulator;

John Devereux

unread,
Feb 23, 2016, 2:36:41 PM2/23/16
to
I am not an expert (ask on comp.arch.embedded) but my understanding is
that this sort of thing is guaranteed by the EABI (bugs aside).



--

John Devereux

David Brown

unread,
Feb 23, 2016, 4:34:17 PM2/23/16
to
I can't even figure out why it would be considered "naughty" at all.
The only implementation-dependent aspects I can see are the alignment of
the parts (on the ARM, 8-bit, 16-bit and 32-bit types all have "natural"
alignments), and the ordering of the bitfields (which is low bits first
- guaranteed by the ARM EABI, and thus consistent across compilers).


bitrex

unread,
Feb 23, 2016, 4:45:57 PM2/23/16
to
"Naughty" if one wants to port the code as-is to another architecture, I
guess.

Tim Wescott

unread,
Feb 23, 2016, 4:53:53 PM2/23/16
to
First:

Yes, it'll work as long as you want the least significant byte to be
named "integer_high" and the most significant byte to be named "frac_low".

Second:

You need to name the bitfield. I'll assume you've named it "bits" --
i.e., phase_accumulator.bits.frac_high.

Third:

It should be just as fast to use:

uint32_t phase_accumulator;

static inline uint8_t integer_high(uint32_t x) {return (x >> 24) & 0xff;}
static inline uint8_t integer_low (uint32_t x) {return (x >> 16) & 0xff;}
static inline uint8_t frac_high (uint32_t x) {return (x >> 8) & 0xff;}
static inline uint8_t frac_low (uint32_t x) {return (x >> 0) & 0xff;}

This will be portable, and integer_high(phase_accumulator) should read as
easily (or more so) as phase_accumulator.bits.integer_high.

--

Tim Wescott
Wescott Design Services
http://www.wescottdesign.com

David Brown

unread,
Feb 23, 2016, 5:21:14 PM2/23/16
to
I would say "non-portable" rather than "naughty". "Naughty" implies
that this might not work in some circumstances, or perhaps depends on
undefined behaviour. This is merely non-portable and relies on
implementation-dependent behaviour (which happens to be defined by the
ARM EABI, and thus should apply to all ARM compilers).

David Brown

unread,
Feb 23, 2016, 5:48:19 PM2/23/16
to
On 23/02/16 22:53, Tim Wescott wrote:
> On Tue, 23 Feb 2016 14:17:59 -0500, bitrex wrote:
>
>> Does anyone know if the following "naughty" (implementation dependent)
>> construct would work "correctly" on the ARM Cortex M4, compiled with
>> arm-gcc-embedded?
>>
>> typedef union {
>> uint32_t phase_accumulator_full;
>> struct {
>> uint8_t integer_high : 8,
>> integer_low : 8,
>> frac_high : 8, frac_low : 8;
>>
>> };
>> } phase_accumulator;
>
> First:
>
> Yes, it'll work as long as you want the least significant byte to be
> named "integer_high" and the most significant byte to be named "frac_low".
>

True.

> Second:
>
> You need to name the bitfield. I'll assume you've named it "bits" --
> i.e., phase_accumulator.bits.frac_high.

Not necessary true, assuming you are using C11 (rather than C++), or gcc
or clang, or any other compiler that supports anonymous structs as an
extension. Since this is specifically for the ARM, and not meant to be
portable, it is perhaps not unreasonable to use an extension that is
available the most common tools for that target. But that's a decision
for the OP to make.

>
> Third:
>
> It should be just as fast to use:
>
> uint32_t phase_accumulator;
>
> static inline uint8_t integer_high(uint32_t x) {return (x >> 24) & 0xff;}
> static inline uint8_t integer_low (uint32_t x) {return (x >> 16) & 0xff;}
> static inline uint8_t frac_high (uint32_t x) {return (x >> 8) & 0xff;}
> static inline uint8_t frac_low (uint32_t x) {return (x >> 0) & 0xff;}
>
> This will be portable, and integer_high(phase_accumulator) should read as
> easily (or more so) as phase_accumulator.bits.integer_high.
>

No, these functions are not a good substitute. First, they do not do
the same thing if "phase_accumulator" is used as a volatile - and such
bitfield structures are usually used for hardware registers where
volatile is relevant. Using the bitfields, accesses to the separate
fields will be done as 8-bit accesses - using the functions, they will
be 32-bit accesses.

Second, your functions give read-only access, not read-write access -
another set is needed for writing. Those are messy.

static inline uint32_t set_integer_low (uint32_t x, uint8_t y) {
return (x & 0xff00ffff) | ((uint32_t) y << 16);
}


Third, your functions will /not/ be as fast for volatile types, and the
write versions may be significantly slower.

Fourth, your functions are ugly in use, especially for setting:

phase_accumulator = set_integer_low(phase_accumulator, y)

(I'm continuing your slight mixup of treating "phase_accumulator" as the
instance of the union, rather than the type.)

Fifth, you lose one of the major reasons for using bitfields rather than
bitmasks. With bitfields, the field is tied tightly to the type, and
therefore the instance of the type. Mistakes are compile-time errors,
your IDE can quickly give you assistance at filling out the field names,
and your debugger can parse the data. With manual shift-and-mask
systems you lose all of that.


The cost of using bitfields here is that the code is not directly
portable to other targets unless they have the same assumptions about
bitfield ordering and alignment (and in reality, most targets do match
here). IMHO, it's a small price to pay.

M Philbrook

unread,
Feb 23, 2016, 8:35:25 PM2/23/16
to
In article <If2zy.23937$Nf2....@fx14.iad>,
bit...@de.lete.earthlink.net says...
sumg ting does not look correct? Assuming I understand what you're
after.

struct _phase_acc {

union { uint32_t phase_accumulator_full };
union { uint_8 integer_high:8,
integer_low:8,
frac_high:8,
frc_low : 8;
};
} phase_accumulator;

You can include a condition to test for the second union to
determine the endious of the platform to correctly compile
the code for the bit field order.

The above struct should overlay to generate a single 32 bit image.

Jamie

Ian Collins

unread,
Feb 23, 2016, 8:49:01 PM2/23/16
to
M Philbrook wrote:
> In article <If2zy.23937$Nf2....@fx14.iad>,
> bit...@de.lete.earthlink.net says...
>>
>> Does anyone know if the following "naughty" (implementation dependent)
>> construct would work "correctly" on the ARM Cortex M4, compiled with
>> arm-gcc-embedded?
>>
>> typedef union {
>> uint32_t phase_accumulator_full;
>> struct {
>> uint8_t integer_high : 8,
>> integer_low : 8,
>> frac_high : 8,
>> frac_low : 8;
>>
>> };
>> } phase_accumulator;
>
> sumg ting does not look correct? Assuming I understand what you're
> after.
>
> struct _phase_acc {
>
> union { uint32_t phase_accumulator_full };

typo..

> union { uint_8 integer_high:8,

typo..
> integer_low:8,
> frac_high:8,
> frc_low : 8;
> };
> } phase_accumulator;
>
> You can include a condition to test for the second union to
> determine the endious of the platform to correctly compile
> the code for the bit field order.
>
> The above struct should overlay to generate a single 32 bit image.

No, it won't. You have two 32 bit unions members of a struct, witch
gives a 64 bit struct.

--
Ian Collins

Tauno Voipio

unread,
Feb 24, 2016, 2:55:16 AM2/24/16
to
A vote on this: At least GCC optimizes these to single bit field
extract instructions on a Cortex.

Alternative format of the same:

static inline uint8_t integer_high(uint32_t x)
{
return (uint8_t)(x >> 24);
}

This should work correctly even without the cast.

--

-TV

Christian Gollwitzer

unread,
Feb 24, 2016, 3:44:50 AM2/24/16
to
Am 23.02.16 um 23:48 schrieb David Brown:
> On 23/02/16 22:53, Tim Wescott wrote:
>> static inline uint8_t integer_high(uint32_t x) {return (x >> 24) & 0xff;}
>> static inline uint8_t integer_low (uint32_t x) {return (x >> 16) & 0xff;}
>> static inline uint8_t frac_high (uint32_t x) {return (x >> 8) & 0xff;}
>> static inline uint8_t frac_low (uint32_t x) {return (x >> 0) & 0xff;}
>>
>> This will be portable, and integer_high(phase_accumulator) should read as
>> easily (or more so) as phase_accumulator.bits.integer_high.
>>
>
> No, these functions are not a good substitute. First, they do not do
> the same thing if "phase_accumulator" is used as a volatile - and such
> bitfield structures are usually used for hardware registers where
> volatile is relevant. Using the bitfields, accesses to the separate
> fields will be done as 8-bit accesses - using the functions, they will
> be 32-bit accesses.

Are you sure? Have you checked the assembly output for this code? For
non-volatile variables at least, I expect that these inline functions
are optimized to the same instructions. I don'T have an ARM compiler
handy, but maybe you could check if it also works with "volatile unit32_t x"

>
> Second, your functions give read-only access, not read-write access -
> another set is needed for writing. Those are messy.
>
> static inline uint32_t set_integer_low (uint32_t x, uint8_t y) {
> return (x & 0xff00ffff) | ((uint32_t) y << 16);
> }
>
>
> Third, your functions will /not/ be as fast for volatile types, and the
> write versions may be significantly slower.
>
> Fourth, your functions are ugly in use, especially for setting:
>
> phase_accumulator = set_integer_low(phase_accumulator, y)

If C++ is used, an inline function with a reference parameter is also
easily optimized:

static inline void set_integer_low (uint32_t x&, uint8_t y) {
x= (x & 0xff00ffff) | ((uint32_t) y << 16);
}

set_integer_low(phase_accumulator, y);

I'm not so sure about a pointer version for pure C

static inline void set_integer_low (uint32_t *x, uint8_t y) {
x= (x & 0xff00ffff) | ((uint32_t) y << 16);
}

set_integer_low(&phase_accumulator, y);

I think the OP should check the assembly output of an optimized build to
decide.

> Fifth, you lose one of the major reasons for using bitfields rather than
> bitmasks. With bitfields, the field is tied tightly to the type, and
> therefore the instance of the type. Mistakes are compile-time errors,
> your IDE can quickly give you assistance at filling out the field names,
> and your debugger can parse the data. With manual shift-and-mask
> systems you lose all of that.

That is a valid reason. But I think that even in pure C, you could get a
warning if you make a typedef unit32_t mybitask; or by embedding it into
a struct with just one member

typedef struct { unit32_t bits; } mybitmask;

Christian

David Brown

unread,
Feb 24, 2016, 4:39:27 AM2/24/16
to
Yes, I have checked - though not for a wide variety of versions,
optimisation flags, etc. The inline functions will result in the same
ARM code in the non-volatile case (assuming at least -O1 optimisation).
But in the volatile case, the compiler correctly generates different
code for the structure access and the manual bitfield extraction.

>>
>> Second, your functions give read-only access, not read-write access -
>> another set is needed for writing. Those are messy.
>>
>> static inline uint32_t set_integer_low (uint32_t x, uint8_t y) {
>> return (x & 0xff00ffff) | ((uint32_t) y << 16);
>> }
>>
>>
>> Third, your functions will /not/ be as fast for volatile types, and the
>> write versions may be significantly slower.
>>
>> Fourth, your functions are ugly in use, especially for setting:
>>
>> phase_accumulator = set_integer_low(phase_accumulator, y)
>
> If C++ is used, an inline function with a reference parameter is also
> easily optimized:
>
> static inline void set_integer_low (uint32_t x&, uint8_t y) {
> x= (x & 0xff00ffff) | ((uint32_t) y << 16);
> }
>
> set_integer_low(phase_accumulator, y);

It is still a mess compared to the bitfield struct access, and it is
still different in the volatile case. Yes, the compiler can optimise
the ugly, unmaintainable and error-prone source code into good object
code - but why not just write clear, simple source code that gets
optimised into good object code?

>
> I'm not so sure about a pointer version for pure C
>
> static inline void set_integer_low (uint32_t *x, uint8_t y) {
> x= (x & 0xff00ffff) | ((uint32_t) y << 16);
> }
>
> set_integer_low(&phase_accumulator, y);

I would expect the compiler to manage this optimisation.

>
> I think the OP should check the assembly output of an optimized build to
> decide.

I think the OP should look first and foremost at the /source/ code, and
decide from that. The code generation details are up to the compiler -
enable optimisation, give it clear and correct source code, and let it
do its job. It is only in very rare situations that the generated code
should be the guide for a decision like this.

>
>> Fifth, you lose one of the major reasons for using bitfields rather than
>> bitmasks. With bitfields, the field is tied tightly to the type, and
>> therefore the instance of the type. Mistakes are compile-time errors,
>> your IDE can quickly give you assistance at filling out the field names,
>> and your debugger can parse the data. With manual shift-and-mask
>> systems you lose all of that.
>
> That is a valid reason. But I think that even in pure C, you could get a
> warning if you make a typedef unit32_t mybitask; or by embedding it into
> a struct with just one member
>
> typedef struct { unit32_t bits; } mybitmask;
>

In both C and C++, you have to embed the item in a struct - "typedef"
itself does not introduce a new type. Yes, this technique can make the
code safer.

But you are going round in circles, writing lots of extra source code,
writing access functions with potentially error-prone masks (imagine a
case where the bitfields were not as simple, or where they were changed
later), with duplicate information in multiple places, and finally
resulting in some unnecessarily verbose and ugly usage.

In the end, all you have done is duplicate a feature that C and C++
support as part of the language. It is a pointless exercise.



David Brown

unread,
Feb 24, 2016, 4:41:37 AM2/24/16
to
I should have mentioned this before, but in the case where your bitfield
sizes match the type, you don't need the bitfield specifiers:

typedef union {
uint32_t phase_accumulator_full;
struct {
uint8_t integer_high;
uint8_t integer_low;
uint8_t frac_high;
uint8_t frac_low;
};
} phase_accumulator;

(Of course, the byte ordering is still wrong - I just wrote it that way
to keep it consistent.)

Steve Pope

unread,
Feb 24, 2016, 7:07:04 PM2/24/16
to
Under K&R C the ordering of fields was not guaranteed, but I
think this was cleaned up by C99 or possibly somewhat later.

OP should see which language standard their compiler uses and
refer to that standard and comment appropriately. (In addition to
testing the code...)

Steve

David Brown

unread,
Feb 24, 2016, 7:29:29 PM2/24/16
to
On 25/02/16 01:06, Steve Pope wrote:
> David Brown <david...@hesbynett.no> wrote:
>
>> On 23/02/16 20:36, John Devereux wrote:
>
>>> bitrex <bit...@de.lete.earthlink.net> writes:
>
>>> I am not an expert (ask on comp.arch.embedded) but my understanding is
>>> that this sort of thing is guaranteed by the EABI (bugs aside).
>
>> I can't even figure out why it would be considered "naughty" at all.
>> The only implementation-dependent aspects I can see are the alignment of
>> the parts (on the ARM, 8-bit, 16-bit and 32-bit types all have "natural"
>> alignments), and the ordering of the bitfields (which is low bits first
>> - guaranteed by the ARM EABI, and thus consistent across compilers).
>
> Under K&R C the ordering of fields was not guaranteed, but I
> think this was cleaned up by C99 or possibly somewhat later.

I haven't looked at K&R for an older definition, but C11 certainly says
that the order of allocation of bit-fields is implementation-dependent.
There are some rules about packing, but there is plenty of scope for
weird implementations if the compiler writers so fancy.

>
> OP should see which language standard their compiler uses and
> refer to that standard and comment appropriately. (In addition to
> testing the code...)
>

As long as his compiler follows the ARM EABI (and all serious embedded
ARM compilers will do so), there should be no problem - the EABI gives
more details for this sort of thing than any of the C or C++ standards.

(He could make it a little easier by dropping the bitfields - they are
not necessary when the struct fields are uint8_t and the bitfields are 8
bits.)


Les Cargill

unread,
Feb 25, 2016, 12:57:21 PM2/25/16
to
If it doesn't, fix it. Endianness is more or less undefined behavior in
'C' so you have to test it.

Walk a 1 through phase_accumulator_full and print out ( or just store
the result and dump it with a JTAG if you are without a console ).

--
Les Cargill

David Brown

unread,
Feb 26, 2016, 11:09:54 AM2/26/16
to
On 25/02/16 19:05, Les Cargill wrote:
> bitrex wrote:
>> Does anyone know if the following "naughty" (implementation dependent)
>> construct would work "correctly" on the ARM Cortex M4, compiled with
>> arm-gcc-embedded?
>>
>> typedef union {
>> uint32_t phase_accumulator_full;
>> struct {
>> uint8_t integer_high : 8,
>> integer_low : 8,
>> frac_high : 8,
>> frac_low : 8;
>>
>> };
>> } phase_accumulator;
>
>
> If it doesn't, fix it. Endianness is more or less undefined behavior in
> 'C' so you have to test it.

No, endianness is not remotely "undefined behaviour" - it is
/implementation/ defined behaviour. There is a huge difference.

This means that the implementation has to document exactly what
endianness is used - and in the case of ARM compilers for embedded
systems, this is specified in the ARM EABI to be little endian.

Being implementation-defined behaviour, this also means that you /can/
test it - sometimes that is easier than trying to find the relevant
documentation! Once you have tested it, you know exactly how it works
for that implementation. And while in theory it could change in later
versions of the compiler, making such a change between compiler versions
would be astoundingly stupid and unhelpful - especially in an embedded
compiler - so it is not going to happen. (I know of only one case where
this happened - and that was on MS's x86 compiler, a long time ago,
where they changed the bitfield endianness.)

If endianness (or bitfield endianness) really were undefined behaviour,
you could not rely on it being the same between two runs of the same
compiler on the same source code - testing would be useless.

Scott Lurndal

unread,
Feb 26, 2016, 12:29:42 PM2/26/16
to
David Brown <david...@hesbynett.no> writes:
>On 25/02/16 19:05, Les Cargill wrote:
>> bitrex wrote:
>>> Does anyone know if the following "naughty" (implementation dependent)
>>> construct would work "correctly" on the ARM Cortex M4, compiled with
>>> arm-gcc-embedded?
>>>
>>> typedef union {
>>> uint32_t phase_accumulator_full;
>>> struct {
>>> uint8_t integer_high : 8,
>>> integer_low : 8,
>>> frac_high : 8,
>>> frac_low : 8;
>>>
>>> };
>>> } phase_accumulator;
>>
>>
>> If it doesn't, fix it. Endianness is more or less undefined behavior in
>> 'C' so you have to test it.
>
>No, endianness is not remotely "undefined behaviour" - it is
>/implementation/ defined behaviour. There is a huge difference.

We have software that is built for both big-endian and little-endian
hosts (and more interestingly, it must internally simulate both big-endian
and little-endian processors).

We have this in one of the project header files.

#include <endian.h>
#ifndef __BYTE_ORDER
#if defined(__BIG_ENDIAN) && !defined(__LITTLE_ENDIAN)
#define __BYTE_ORDER __BIG_ENDIAN
#elif !defined(__BIG_ENDIAN) && defined(__LITTLE_ENDIAN)
#define __BYTE_ORDER __LITTLE_ENDIAN
#define __BIG_ENDIAN 4321
#elif !defined(__BIG_ENDIAN) && !defined(__LITTLE_ENDIAN)
#define __BIG_ENDIAN 4321
#define __BYTE_ORDER __BIG_ENDIAN
#else
#error Unable to determine Endian mode
#endif
#endif

#if !defined(htobe16)
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define htobe16(x) __bswap_16 (x)
# define htole16(x) (x)
# define be16toh(x) __bswap_16 (x)
# define le16toh(x) (x)

# define htobe32(x) __bswap_32 (x)
# define htole32(x) (x)
# define be32toh(x) __bswap_32 (x)
# define le32toh(x) (x)

# define htobe64(x) __bswap_64 (x)
# define htole64(x) (x)
# define be64toh(x) __bswap_64 (x)
# define le64toh(x) (x)
# else
# define htobe16(x) (x)
# define htole16(x) __bswap_16 (x)
# define be16toh(x) (x)
# define le16toh(x) __bswap_16 (x)

# define htobe32(x) (x)
# define htole32(x) __bswap_32 (x)
# define be32toh(x) (x)
# define le32toh(x) __bswap_32 (x)

# define htobe64(x) (x)
# define htole64(x) __bswap_64 (x)
# define be64toh(x) (x)
# define le64toh(x) __bswap_64 (x)
# endif
#endif

Then use hto{bl}eXX and {bl}e64tohXX in
the code without the need for local ifdefs.

David Brown

unread,
Feb 26, 2016, 12:42:57 PM2/26/16
to
On 26/02/16 18:29, Scott Lurndal wrote:
> David Brown <david...@hesbynett.no> writes:
>> On 25/02/16 19:05, Les Cargill wrote:
>>> bitrex wrote:
>>>> Does anyone know if the following "naughty" (implementation dependent)
>>>> construct would work "correctly" on the ARM Cortex M4, compiled with
>>>> arm-gcc-embedded?
>>>>
>>>> typedef union {
>>>> uint32_t phase_accumulator_full;
>>>> struct {
>>>> uint8_t integer_high : 8,
>>>> integer_low : 8,
>>>> frac_high : 8,
>>>> frac_low : 8;
>>>>
>>>> };
>>>> } phase_accumulator;
>>>
>>>
>>> If it doesn't, fix it. Endianness is more or less undefined behavior in
>>> 'C' so you have to test it.
>>
>> No, endianness is not remotely "undefined behaviour" - it is
>> /implementation/ defined behaviour. There is a huge difference.
>
> We have software that is built for both big-endian and little-endian
> hosts (and more interestingly, it must internally simulate both big-endian
> and little-endian processors).
>
> We have this in one of the project header files.

<snip details to save electrons>

> Then use hto{bl}eXX and {bl}e64tohXX in
> the code without the need for local ifdefs.
>

Yes, that looks like a reasonable solution if you need to handle
different endiannesses. Another possibility will come with gcc 6, which
supports attributes for structs with specific endiannesses (and I know
at least one compiler that has had that feature for perhaps a couple of
decades).

But if your code does not have to be portable - such as if you are
modelling a hardware register on a particular device - you can just use
the one fixed layout.

Les Cargill

unread,
Feb 26, 2016, 8:00:06 PM2/26/16
to
David Brown wrote:
> On 25/02/16 19:05, Les Cargill wrote:
>> bitrex wrote:
>>> Does anyone know if the following "naughty" (implementation dependent)
>>> construct would work "correctly" on the ARM Cortex M4, compiled with
>>> arm-gcc-embedded?
>>>
>>> typedef union {
>>> uint32_t phase_accumulator_full;
>>> struct {
>>> uint8_t integer_high : 8,
>>> integer_low : 8,
>>> frac_high : 8,
>>> frac_low : 8;
>>>
>>> };
>>> } phase_accumulator;
>>
>>
>> If it doesn't, fix it. Endianness is more or less undefined behavior in
>> 'C' so you have to test it.
>
> No, endianness is not remotely "undefined behaviour" - it is
> /implementation/ defined behaviour. There is a huge difference.
>

Ah, there you go. Right! I'd forgotten the term of art.

<snip>

--
Les Cargill

Allan Herriman

unread,
Feb 27, 2016, 3:59:15 AM2/27/16
to
It happened to me on an 8051 C compiler in the '90s. The endianness of
bitfields changed between one compiler version and the next.
I learned a lesson, and have not used bitfields in C since then.

I believe the compiler vendor is still in business today (well, they were
bought by a microcontroller manufacturer).

Regards,
Allan

David Brown

unread,
Feb 27, 2016, 8:37:59 AM2/27/16
to
Thanks - now I know of /two/ cases where compilers have changed bit
ordering!

> I learned a lesson, and have not used bitfields in C since then.

You are throwing out the baby with the bathwater. If you avoid using a
feature just because a small-time compiler writer for a brain-dead and
C-unfriendly processor made an inconvenient decision two decades ago,
you would have to give up programming altogether. Bitfields have
portability issues, but can be extremely useful - they are perfectly
safe to use once you understand them.

Öö Tiib

unread,
Feb 27, 2016, 10:23:57 AM2/27/16
to
+1. Not only small-time. All the versions of "big-time" compilers have
been defective in one or other way. There are only (more or less)
defective tools and libraries and platforms. Getting the work done
with such is part of skill. Without desire to acquire that skill it
is impossible to make software.

David Brown

unread,
Feb 27, 2016, 10:35:00 AM2/27/16
to
Changing the bitfield order between versions is not really a "defect" as
such - it's a design decision, not a bug. When MS did it (and I guess
they count as "big-time"), they moved to little-endian ordering because
it was much more common for other tools on the x86 and other
little-endian processors. So it was a good decision, but it came as a
surprise to people who had upgrade compiler versions and found their
code changed functionality.

But yes, even "big-time" compilers have bugs. Some, like gcc and clang,
are very open about them - some others prefer to keep them secret until
you open a support case (I don't mean to imply that all closed-source
compiler manufacturers keep their bugs secret, as that would be far from
true). It seems there is little relationship between the cost of tools
and their quality. Certainly the last compiler bugs I have seen have
been in very expensive, big-name tools for a brain-dead microcontroller.
Those bugs turned up on simple, clear code - nothing complicated or
bleeding-edge. (I would prefer not to name the tools as I have only
helped out another developer here, rather than studying the tool in detail.)

Öö Tiib

unread,
Feb 27, 2016, 1:14:38 PM2/27/16
to
On Saturday, 27 February 2016 17:35:00 UTC+2, David Brown wrote:
I meant it more generally. People who ever want to port their code to
different version or different compiler or different platform should
be prepared that there are some noisy and some silent breaking changes.
Some are because of implementation-defined features like that binary
representation of bitfield some are because of defects. Defects are
most likely in the code compiled but sometimes also in previous or
new implementation.

>
> But yes, even "big-time" compilers have bugs. Some, like gcc and clang,
> are very open about them - some others prefer to keep them secret until
> you open a support case (I don't mean to imply that all closed-source
> compiler manufacturers keep their bugs secret, as that would be far from
> true). It seems there is little relationship between the cost of tools
> and their quality. Certainly the last compiler bugs I have seen have
> been in very expensive, big-name tools for a brain-dead microcontroller.
> Those bugs turned up on simple, clear code - nothing complicated or
> bleeding-edge. (I would prefer not to name the tools as I have only
> helped out another developer here, rather than studying the tool in detail.)

Such things happen rarely may be 1-3 times a year but I have also feeling
that if a tool costs like $2K or more per license then it is a warning
sign about potential quality issues.

Jerry Stuckle

unread,
Feb 27, 2016, 4:40:15 PM2/27/16
to
On 2/27/2016 10:34 AM, David Brown wrote:
I remember the Borland Turbo C++ compiler back in the early-mid 90's.
They never admitted they had any bugs in their compilers, but every year
or so they came out with another version (which you had to pay for) that
"fixed" most of the bugs. Except you then had to find a whole new set
of bugs.

Plus they charged for reporting a bug. I remember one basic C++ course
where a student's code wasn't working right. In my hotel that night, I
found the problem - he was passing an object by reference, and on return
the object's destructor was called. I called Borland to report it, and
they wanted something like $35 or $40 at that time just to let them know
they had a bug. I didn't pay, but the next version had it fixed. New
bugs in it, though.

I actually think this was a big cause of the eventual failure of their
compiler. It was cheap, fast, and once you knew the bugs, pretty good.
But it got a bad reputation for having bugs; one that was only partly
deserved.

The bottom line is - you aren't going to hide bugs. They will be found,
and people talk.

--
==================
Remove the "x" from my email address
Jerry Stuckle
jstu...@attglobal.net
==================

Paavo Helde

unread,
Feb 27, 2016, 5:48:12 PM2/27/16
to
On 27.02.2016 23:40, Jerry Stuckle wrote:
> I remember the Borland Turbo C++ compiler back in the early-mid 90's.
> They never admitted they had any bugs in their compilers, but every year
> or so they came out with another version (which you had to pay for) that
> "fixed" most of the bugs. Except you then had to find a whole new set
> of bugs.
>
> Plus they charged for reporting a bug. I remember one basic C++ course
> where a student's code wasn't working right. In my hotel that night, I
> found the problem - he was passing an object by reference, and on return
> the object's destructor was called. I called Borland to report it, and
> they wanted something like $35 or $40 at that time just to let them know
> they had a bug. I didn't pay, but the next version had it fixed. New
> bugs in it, though.
>
> I actually think this was a big cause of the eventual failure of their
> compiler.

Except that it is not dead. Nowadays this is called Embarcadero
C++Builder and the last release according to Wikipedia was on 31 Aug.
2015 (C++Builder 10 Seattle (23)).






Jerry Stuckle

unread,
Feb 27, 2016, 6:21:26 PM2/27/16
to
C++ Builder is a derivative of Turbo C++, but it is not Turbo C++, nor
is it owned by Borland any longer. It has changed significantly since
Borland owned it.

Paavo Helde

unread,
Feb 27, 2016, 6:33:55 PM2/27/16
to
So... and which part of this means "eventual failure of [Turbo C++]"?

Of course it has evolved significantly over the years, and there have
been name changes and sell-offs (to shake off some alleged bad
reputation or not, I don't know), but as far as I can see there is no
"eventual failure".


Jerry Stuckle

unread,
Feb 27, 2016, 8:16:07 PM2/27/16
to
On 2/27/2016 6:33 PM, Paavo Helde wrote:
> On 28.02.2016 1:21, Jerry Stuckle wrote:
>> On 2/27/2016 5:47 PM, Paavo Helde wrote:
>>> On 27.02.2016 23:40, Jerry Stuckle wrote:
>>>
>>>> I actually think this was a big cause of the eventual failure of their
>>>> compiler.
>>>
>>> Except that it is not dead. Nowadays this is called Embarcadero
>>> C++Builder and the last release according to Wikipedia was on 31 Aug.
>>> 2015 (C++Builder 10 Seattle (23)).
>>>
>>
>> C++ Builder is a derivative of Turbo C++, but it is not Turbo C++, nor
>> is it owned by Borland any longer. It has changed significantly since
>> Borland owned it.
>
> So... and which part of this means "eventual failure of [Turbo C++]"?
>

The part where Borland failed with it.

> Of course it has evolved significantly over the years, and there have
> been name changes and sell-offs (to shake off some alleged bad
> reputation or not, I don't know), but as far as I can see there is no
> "eventual failure".
>

The current product is nothing like the failed one. Just because there
is a derivative does not mean the original was successful.

David Brown

unread,
Feb 28, 2016, 9:26:40 AM2/28/16
to
On 27/02/16 19:14, 嘱 Tiib wrote:
> On Saturday, 27 February 2016 17:35:00 UTC+2, David Brown wrote:
Agreed.

> Some are because of implementation-defined features like that binary
> representation of bitfield some are because of defects. Defects are
> most likely in the code compiled but sometimes also in previous or
> new implementation.

Agreed. It is important to remember that "implementation defined
behaviour" can vary between compiler versions. New compiler versions
can have new bugs in the compiler - they can also have new optimisations
or changes to code generation that causes different behaviour in code
that had undefined behaviour but happened to work on the other compiler.

For the kind of development I do, I rarely change compiler in the middle
of a project - and if I have to make changes to an old project, I first
bring up the old tools and check that I can do a bit-perfect rebuild of
the old version.
0 new messages