Author Topic: C word  (Read 51953 times)

0 Members and 1 Guest are viewing this topic.

Offline AndyC_772

  • Super Contributor
  • ***
  • Posts: 4563
  • Country: gb
  • Professional design engineer
    • Cawte Engineering | Reliable Electronics
Re: C word
« Reply #50 on: September 18, 2019, 08:57:37 am »
Indeed, why would you NOT choose to use these?

Surely if code is to be bug-free and portable, its meaning must first be predictable?

I've never liked the concept of "the width of this type is compiler dependent"; it's just asking for things to break when you port code between different architectures.

Offline PerranOakTopic starter

  • Frequent Contributor
  • **
  • Posts: 581
  • Country: gb
Re: C word
« Reply #51 on: September 18, 2019, 02:42:19 pm »
BTW when the same compiler compiles a C-prog does it always create EXACTLY the same code?
That is, if you compile it, do nothing to the code then compile it again are those two files of machine-code the same?
You can release yourself but the only way to go is down!
RJD
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #52 on: September 18, 2019, 02:47:48 pm »
Of course. You should definitely use stdint types when you need integer types with a fixed, guaranteed size.
In cases when you don't, the base C types can still be adequate as long as you follow the minimum ranges the standard guarantees.

This is C99 though, and unfortunately, in a very few cases, you may not have access to a C99-compliant compiler. Either because your platform is too old and doesn't have any, or because allowed compilers can only be C89/90 compliant (which is still the case in a few safety-critical environments, fortunately not all!) In this case, the old and recommended way is still to define your own integer types in an implementation-specific manner in a separate header, and mark it clearly as non-portable. A few older compilers already did this, so if there's such a compiler-provided header, use it instead of your own, obviously!

As to the width of integers, this "issue" is absolutely not specific to C. In many high-level languages, the base "integer" type doesn't have any specific width either, and is implementation-specific. If the language is standardized, there may just be a minimum guaranteed width. The idea may be debatable of course, but it also goes with the fact they are high-level languages, so it kind of makes sense.

In any case, NEVER write code that assumes a specific width larger than the minimum guaranteed one, and if you must do so, use standard constructs (like stdint in C99+) or custom ones that are CLEARLY isolated and documented.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #53 on: September 18, 2019, 02:52:36 pm »
BTW when the same compiler compiles a C-prog does it always create EXACTLY the same code?
That is, if you compile it, do nothing to the code then compile it again are those two files of machine-code the same?

With all the C compilers I have ever used, that was the case.
I don't think there is any strict guarantee on this based on standards, though. So a specific compiler may for instance implement some optimizations using some random factors inside some of the algorithms, which could yield a different object code every time it compiles. But that would be kind of weird and non-predictable, so I don't think compiler designers have been inclined to doing so. It would also make automatic testing of the compilers a lot harder, so that wouldn't really make sense.
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 8848
  • Country: 00
Re: C word
« Reply #54 on: September 18, 2019, 02:59:30 pm »
Quote
That is, if you compile it, do nothing to the code then compile it again are those two files of machine-code the same?

Good question :)

I would expect them to be, yes. In fact, back when BC++3.1 was a thing for embedded C, we  hit a situation when the same code was producing different binaries on different build PCs. There were only ever two versions (that is, some version of the code produced a binary A on one PC, B on another, but there was never a C), but it should have been the same and we spend some effort in finding out why they were different and then fixing it. Can't recall the cause now, though.
 

Offline janoc

  • Super Contributor
  • ***
  • Posts: 3958
  • Country: de
Re: C word
« Reply #55 on: September 18, 2019, 05:40:12 pm »
Here is a good resource on modern C language - e.g. did you know that C includes threads and atomics (nothing to do with bombs) as part of the standard library now?

http://modernc.gforge.inria.fr/

IMO, this is a much more relevant than the old K&R book which describes a fairly obsolete standard of the language - the second edition covering the "ANSI C" (not the original K&R C) came out in 1988! C had several revisions of the standard since and the book has not been updated.
« Last Edit: September 18, 2019, 05:47:09 pm by janoc »
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2501
  • Country: pl
Re: C word
« Reply #56 on: September 18, 2019, 06:17:57 pm »
Indeed, why would you NOT choose to use these?
I do not see any relationship between the presented issue and using exact-width types. The sub-topic was about standard conformance of the toolsets and the code doesn’t affect that in any way.

However, let me answer your question nonetheless, because it’s another trap. Putting aside exotic architectures which don’t have those exact-width types(1), the problem is that those are merely aliases. Calculations are never performed in the exact-width domain and int size affects the calculations the same way as it would without using exact-width types. If you replace unsigned short with uint16_t in the undefined behaviour example I’ve posted earlier, it will change nothing. It will still cause a signed(!) overflow in a 32-bit(!) variable.

This is the thing I was talking about earlier. People guess the meaning of their code using their intuition, instead of learning what it actually means. The misconception, that declaring a variable as (u)intN_t will make calculations limited to N bits and — for unsigned types — being performed as unsigned, is widespread for that reason. But C not only makes no such guarantee, but specifically makes that behaviour impossible. The only reason why it “works” (with strong emphasis on the quotation marks ;)) is a combination of two factors. First: compilers are allowed to do anything with code containing UB. As it happens, the easiest thing is ignoring the corner cases and emitting code that would work for valid code. Second: nearly all platforms you are dealing with nowadays are using two’s-complement representation for integers. The effect: wrong calculation is performed and yields an invalid result, but if it is later interpreted as its perceived type it “magically” has the value you expected it to have. But, technically, this is a pure accident.

uint8_t, int8_t, int_least8_t and uint_least8_t are always useless(2), because the only type they could ever alias is (un)signed char.

I've never liked the concept of "the width of this type is compiler dependent"; it's just asking for things to break when you port code between different architectures.
This is not a random idea. It’s the effect of platforms supporting different types of data. The code will never break, as long as you write it properly, understanding what the code means — instead of guessing.
____
(1) All exact-width types are optional and in fact nearly all are never provided — that applies to all available toolsets. Even the paragrap 3 ones hapen to be missing: a nice edge case is avr-gcc not having uint64_t with 8-bit int.
(2) Unless you want to save some keystrokes.
Why 📎 | We live in times when half of people have IQ below 100.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8066
  • Country: pl
Re: C word
« Reply #57 on: September 18, 2019, 08:03:18 pm »
Here's a fun trick that will cure you from thinking that using stdint.h is a panacea :D
Code: [Select]
#include <stdint.h>

int main() {
        uint8_t a = 128;
        int8_t b = -128;
        volatile int8_t *c = 0;
        *c = a/b;
}
Compiled without -mint8 writes -1 to NULL.
Complied with -mint8 writes +1 to NULL.
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #58 on: September 18, 2019, 09:10:54 pm »
I do not see any relationship between the presented issue and using exact-width types.

In your mind, you are more focused to the operation size if you write it explicitly, and this helps people at matching test-cases: for - expted - and - actual - values.
« Last Edit: September 18, 2019, 09:38:13 pm by legacy »
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #59 on: September 18, 2019, 09:21:07 pm »
Calculations are never performed in the exact-width domain

On m68k, the ALU has specific instructions with the size {1,2,4} (={.b, .w, .l} in the motorola assm syntax)  both available for signed and unsigned types, and the overflow and underflow flags are set accordingly to the instruction.

RISCs ... are naughty boys, a completely different story  :D
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #60 on: September 18, 2019, 09:29:28 pm »
Anyway, even the C compiler "SierraC/m68k" (one of the impressive in the 90s and 2000s)  simply does not care, not even if potentially the m68k hardware could help for free since the flags are already calculated, whereas on RISC (e.g. with MIPS) the ALU does not calculate them, and you would need to add a lot of extra instructions.

It's that ... it's the C language that is a bit rotten about that  :-//
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #61 on: September 18, 2019, 09:55:50 pm »
Note that here again, actually reading the standard helps.

Summing it up "quickly":

- stdint.h defines integer types that are guaranteed to be of an exact width, and others that are guaranteed to be at least some specified width. Those are respectively intN_t/uint_Nt and uint_leastN_t/int_leastN_t (the latter are well less known).
- None of the former are actually REQUIRED to be implemented. Your particular implementation may contain NONE, or just some of them. It's pretty rare that there be none, but the 64-bit ones may, for instance, not be implemented. The standard says: "these types are optional".
- For the latter, as far as I've gotten it, 8, 16, 32 and 64 are REQUIRED. Others are optional. So a conforming C99 compiler shoud actually define int_least64_t for instance, but may not define int64_t. The standard says: "The following types are required:" and lists int_leastN_t/uint_leastN_t for N=8, 16, 32, 64. (I would then conclude that any compiler NOT defining int_least64_t, for instance, IS non-conforming. But it may absolutely not define int64_t.)
- Some implementations may define other, more exotic widths such as 24 bits. This is allowed, but strictly optional of course, and in practice, a lot less common than the more ubiquitous 8, 16, 32, 64 (which again may not all be defined either...)
- There are yet other categories of integer types, such as intptr_t/uintptr_t, which are useful everytime you need to cast a pointer to an integer. This is the only conforming way of doing it. But those types are also optional per the standard. They are commonly defined on implementations targetting platforms with non-segmented addresses, and may not be defined on the latter. This is of course just a technical constraint, nothing is written about that in the standard.
- AFAIK, nothing specific is said about arithmetic with stdint types, so those merely define 'storage', which is kinda obvious in a way, since in C, all a typedef can do is define a derived type, not how operations should be performed on them. Thus, we can infer from the standard that stdint types just inherit the rules from the base integer C types. Which is, admittedly, not very consistent (but that is, OTOH, understandable, as the comittee decided to provide stdint's only through a standard header, and not through any extension of the language itself. This point is important. To ensure proper arithmetics with those types, it's definitely not trivial. You need cautious casting of intermediate computations all over the place, and you need not to assume anything much. To understand this particular point, you need to have a (very) good understanding of the C language.

Of course, the moral of the story is NOT that you shouldn't use some particular standardized feature (like for instance stdint here) just because many points of it are optional per the standard. Of course, you always have to know your tools. If something is not available on your particular implementation, you will obviously not be able to use it. But you should definitely use all that's available, and find decently elegant ways of implementing the rest (as I said, in seperate headers/source files).

And yes you also have to dig a little deeper... and stdint.h is actually a good example of why reading the standard will definitely help. You're not likely to find in-depth knowledge of how to use stdint.h types properly in any cheat sheet, and not that likely to find that in many books on C.

Finally, this is also an example of C being an high-level language (despite what some may say). stdint's are provided for data structures, storage and data exchange, not for fixed-width arithmetics. C is not assembly. C is not object-oriented either (at least per se), so you can't redefine operators for new types, which would have allowed guaranteed, fixed-width arithmetic for each integer type defined in stdint.h...

As I said before, many other high-level languages would exhibit the same "problem" with arithmetics. And yes, you can work around that with those that can redefine basic operators.
In C, it takes a lot more careful approach.
« Last Edit: September 18, 2019, 09:57:59 pm by SiliconWizard »
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #62 on: September 18, 2019, 10:21:04 pm »
all a typedef can do is define a derived type, not how operations should be performed on them

That's *the* problem, and why I said "rotten"  :D
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #63 on: September 18, 2019, 10:39:25 pm »
all a typedef can do is define a derived type, not how operations should be performed on them

That's *the* problem, and why I said "rotten"  :D

Just a matter of perspective. If you want to use a true OO language, use one. Now defining your own types and operations on them with standard operators is also a slippery slope, hiding what goes on behind the scenes.

The idiomatic C approach, in which you can't overload operators, is to define functions instead. May not look as nice syntactically, but it works, and it makes it a bit more clear what you are actually doing. You don't have to guess how a given operator is going to be implemented in a given statement... which can cause horror stories as well.

Edit: That said, and if you were thinking about that: I admit some languages handle this much better than C. Guess what. Ada for instance. ;D
Many people probably don't suspect that Ada actually allows you to define and use very low-level stuff in a clean and consistent way.
Integer ranges just blow away any C stdint.h attempt, and behave in a logical way.
Even bit fields are much easier and predictable in Ada.  ::)

How about that?
Code: [Select]
subtype Byte_t is Natural range 0..255;  -- There you have your [0..255] unsigned integer. But that's not all...
-- As is, a Byte_t can't be greater than 255. It won't roll-over. It will raise an error if you attempt to increment one that is 255.
-- Alternately, if you want Byte_t arithmetic to roll-over, as many low-level programmers would expect, declare it as so instead:
type Byte_t is mod 256;
for Byte_t'Size use 8;  -- Now it makes sure Byte_t has actually 8-bit storage on top of "acting" like an 8-bit unsigned integer...
-- Now some cherry on the cake, give Byte_t a default value:
type Byte_t is mod 256 with Default_Value => 0;
-- And I haven't even given examples of bit fields yet...

I love C, but the more I know Ada and the more it actually looks refreshing... ;D
« Last Edit: September 18, 2019, 11:33:29 pm by SiliconWizard »
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 8848
  • Country: 00
Re: C word
« Reply #64 on: September 19, 2019, 07:28:22 am »
Quote
Here's a fun trick that will cure you from...

using inappropriate types. Should be a uint_least16_t in use there.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8066
  • Country: pl
Re: C word
« Reply #65 on: September 19, 2019, 08:15:33 am »
No, should be int_least16_t because one is allowed to be negative ;)

Or even better, int_least9_t, in the event that there is some platform with efficient 9 or 12 bit ints :popcorn:

But of course we don't want to waste storage space, so both variables should be defined as they are and only cast to a wider type right before calculation.
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #66 on: September 19, 2019, 10:44:53 am »
Code: [Select]
boolean_t check_exactwidth()
{
     boolean_t ok;

     ok is True;
     ok And is { language'uint8_t'size , language'uint16_t'size , language'uint32_t'size }
                   NotEqualTo? { Undefined, Undefined, Undefined };
     ok And is { language'uint8_t'size , language'uint16_t'size , language'uint32_t'size }
                   UndefinedOrEqualTo? { 1, 2, 4 };
     ok And is { language'uint8_t'range , language'uint16_t'range , language'uint32_t'range }
                   UndefinedOrEqualTo? { machinelayer'uint8_t'range, machinelayer'uint16_t'range, machinelayer'uint32_t'range };
     ok And is machinelayer'property'overflowcareful EqualTo? True;
     ok And is machinelayer'property'underflowcareful EqualTo? True;
     ok And is machinelayer'property'carrycareful EqualTo? True;

     ans is ok;
}

void early_check()
{
    ...
    is check_exactwidth() EqualTo? False
    {
         do_panic(module, id, "exact_width not possible, the following algorithm won't work properly");
    }
    ...
}

This is how my Arise-v2 HL language operates. It's not C, and there is a clear separation among { language, machine layer, machine hardware } since the language is an abstract representation of the machine layer, which is how the language is implemented in the hardware.

So, you have the machine layer, where the implementation goes, and there is a mechanism able to report details to the user, who is using the language to express his/her needs.

is_exact_width() executed on an m68k will report True not because the hardware is able to perform exact_width calculations but rather because, and only if, the machine layer has been implemented to take advantage of it.

Of course, it's a private researching stuff, nothing serious, and I don't have the arrogance to reinvent any language used in the industry (and none in the squad has enough competence and time to make it serious), it's only how my team has had fun with our softcore :D
« Last Edit: September 19, 2019, 10:49:23 am by legacy »
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #67 on: September 19, 2019, 02:46:08 pm »
(...)
This is how my Arise-v2 HL language operates. It's not C, and there is a clear separation among { language, machine layer, machine hardware } since the language is an abstract representation of the machine layer, which is how the language is implemented in the hardware.

That looks obviously a bit cryptic to me (without the manual  ;D ), but interesting.
Did you/your team write a full compiler for that? If so, did you write it 100% from scratch, or did you use any third-party tools to help?
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #68 on: September 19, 2019, 03:10:26 pm »
That looks obviously a bit cryptic to me

Oh, well ... one of the "designer" (so she calls herself) is a Bulgarian girl, who loves Erlang so much that ... wanted to make everything Erlang-like. We had to work hard to convince her to have ... *cough* *cough* ... "compromises". So, if you say that the above is "cryptic", well ... don't look at Erlang stuff, and still better, don't look at her earlier works, because it's even more cryptic :D


(without the manual  ;D )

ma.nu.what?  :D

Did you/your team write a full compiler for that? If so, did you write it 100% from scratch, or did you use any third-party tools to help?

Everything from scratch, written in C, so this is proof that we don't dislike the C language. Expressions are evaluated in RPN form, and we have an AST-approach.

The HL has been only partially implemented because the machine Layer of Arise-v2 is not stable, but rather prone to changes. This doesn't help.

-

*my* point was: we prefer to have details built-in the Compiler rather than in an external #include file, and we prefer to have a clear separation among { language, machine layer, and hardware }. This really helps.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #69 on: September 19, 2019, 03:34:52 pm »
So, if you say that the above is "cryptic", well ... don't look at Erlang stuff, and still better, don't look at her earlier works, because it's even more cryptic :D

I took another look the other day at Erlang after we mentioned it. It turns out "simpler" than I remembered, but the all-functional approach and the debatable syntax don't really help indeed.
Now would I consider future developments in Erlang? Not really. ;D (Also because it basically runs on a virtual machine as far as I got, I hate those.)

*my* point was: we prefer to have details built-in the Compiler rather than in an external #include file, and we prefer to have a clear separation among { language, machine layer, and hardware }. This really helps.

I am actually currently working on some kind of extension of C (with modules, and other things), the parser/analyzer of which is written in C, so I can relate to this point. I considered the preprocessor-only approach to be clunky, not fail-safe, and very limited.
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #70 on: September 19, 2019, 07:27:21 pm »
I am actually currently working on some kind of extension of C (with modules, and other things)

using llvm?

what surprises me is ... why in 20 years nobody has already tried to *improve* the C language without making it *too much improved* like it happens with the C++ ?
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17805
  • Country: fr
Re: C word
« Reply #71 on: September 19, 2019, 07:44:45 pm »
I am actually currently working on some kind of extension of C (with modules, and other things)
using llvm?

Not at the moment. I may consider this later on.

what surprises me is ... why in 20 years nobody has already tried to *improve* the C language without making it *too much improved* like it happens with the C++ ?

I don't know. Maybe, or probably, because there are too many different views of what a better C should be?
There have been MANY attempts, and not just C++. (D for instance, and a lot of others, more or less close to C.) But I agree those were almost always designed to be completely new languages, and not just improvements on C.

The C standard comittee has been pretty conservative all in all. Not saying this is bad per se, it avoids too much "fragmentation". But then, it only improves C slowly and marginally...

Like, I'm adding support for "modules" (trying something clean, no more header files, no more namespace problems, and, generic modules). I'm suspecting that many people might not care for that, and would like some OO constructs instead (simpler than C++). Or built-in support for parallel computing (I might add some features about that later, that said). Or better type definition. Etc. So many things can be improved, and we probably almost all have a different idea of what it should be.

« Last Edit: September 19, 2019, 07:46:19 pm by SiliconWizard »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: C word
« Reply #72 on: September 20, 2019, 11:56:21 am »
BTW when the same compiler compiles a C-prog does it always create EXACTLY the same code?
That is, if you compile it, do nothing to the code then compile it again are those two files of machine-code the same?
The machine code in the object files should be exactly the same (assuming no libraries, the compiler, or compiler options are changed in the mean time) but the object files often contain other data that does vary; for example, the compilation timestamp.

If the code contains something like
    const char  built[] = "Compiled on " __DATE __ " " __TIME__;
then the contents of that string vary based on when the code is compiled, and because it is marked as an immutable string, it might reside in the code section, depending on how it is linked.  (The linker can merge sections, if told to do so.)

For object files compiled using GNU tools, I might use
    diff <(objdump -d FILE1.o) <(objdump -d FILE2.o)
(for code comparison), or
    diff <(readelf -x .text -x .data FILE1.o) <(readelf -x .text -x .data FILE2.o)
(for code and initialized data comparison), or
    diff <(objdump -s FILE1.o | sed -e '/^$/d; /^[^ ]/d') <(objdump -s FILE2.o | sed -e '/^$/; /^[^ ]/d')
(for comparing all sections, but ignoring file name and section names).

The <(command) redirection above is a Bash extension, that is expanded to a path to a pipe or file containing the output of command.
That is, diff <(cat FILE1) <(cat FILE2) is equivalent to diff FILE1 FILE2 .  If you happen to use Busybox or dash instead of Bash, use temporary files instead.
 

Offline FreddieChopin

  • Regular Contributor
  • *
  • !
  • Posts: 102
  • Country: ua
Re: C word
« Reply #73 on: September 20, 2019, 04:47:19 pm »
I am actually currently working on some kind of extension of C (with modules, and other things)

using llvm?

what surprises me is ... why in 20 years nobody has already tried to *improve* the C language without making it *too much improved* like it happens with the C++ ?

Stick welding hasn't changed for decades either and yet gets the job done every day. You can't improve a perfect solution.
 

Offline Bassman59

  • Super Contributor
  • ***
  • Posts: 2499
  • Country: us
  • Yes, I do this for a living
Re: C word
« Reply #74 on: September 22, 2019, 07:43:15 pm »

Edit: That said, and if you were thinking about that: I admit some languages handle this much better than C. Guess what. Ada for instance. ;D
Many people probably don't suspect that Ada actually allows you to define and use very low-level stuff in a clean and consistent way.
Integer ranges just blow away any C stdint.h attempt, and behave in a logical way.
Even bit fields are much easier and predictable in Ada.  ::)

How about that?
Code: [Select]
subtype Byte_t is Natural range 0..255;  -- There you have your [0..255] unsigned integer. But that's not all...
-- As is, a Byte_t can't be greater than 255. It won't roll-over. It will raise an error if you attempt to increment one that is 255.
-- Alternately, if you want Byte_t arithmetic to roll-over, as many low-level programmers would expect, declare it as so instead:
type Byte_t is mod 256;
for Byte_t'Size use 8;  -- Now it makes sure Byte_t has actually 8-bit storage on top of "acting" like an 8-bit unsigned integer...
-- Now some cherry on the cake, give Byte_t a default value:
type Byte_t is mod 256 with Default_Value => 0;
-- And I haven't even given examples of bit fields yet...

I love C, but the more I know Ada and the more it actually looks refreshing... ;D

It’s worth noting that VHDL has had this sort of range feature since forever. (VHDL borrows from Ada, of course.) It’s something Verilog never had — does SystemVerilog have such a feature?
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->