Rules of c language are very confusing

ApacheKid

Joined Jan 12, 2015
1,533
and adding new keywords to C or the existence of reserved works is a problem that doesn't really exist for the vast majority of embedded programmers. A few new reserved words have been added over the years in C99 and C11.
Well people who see no shortcomings in C are of course going to show little interest in considering improvements, I have no idea what the "majority of embedded programmers" think about this subject, but as someone with experience of developing compilers and parsers and optimizers I do have some insights into the question.
 

ApacheKid

Joined Jan 12, 2015
1,533
A simple example of the kind of thing I'd like to see removed when designing a new language, is the need for forward declarations. There is absolutely no good reason for these other than the chosen grammatical rules. It is quite straightforward to design a language front end that doesn't need these.

Then I'd like to see a keyword like forever or repeat or always to represent repeating loop where the exit (if there is one) is handled inside the loop logic, we all know that such loops are shunned but there's plenty of times people write for(;;; ) or while(1) and so on, not everyday but it does sometimes make sense so a dedicated keyword is entirely reasonable.

The large number of numeric types could be reduced too, there are just too many ways to do this with stuff like uint_fast16_t and int_least32_t and umpteen others, there has to be a much simpler and more elegant way to address the issue that these contrivances are trying to address.
 

Papabravo

Joined Feb 24, 2006
21,159
I don't think any of those are very serious issues. Maybe I'm missing something, and I just don't understand the argument.
  1. If forward declarations are those that occur before an identifier is used, then I don't see that as a major flaw. Gone are the days when there at least was an imperative to compile or assemble in a single pass. I don't think allowing only a single pass through the source is done any more by anybody, but I could be wrong.
  2. C already has the do { ...statements..} while <condition> ; construct. This is effectively the same as repeat { ...} until <condition> ; If you must exit from inside the loop there is the break ; keyword.
  3. I suppose you could substitute the number types with a single type keyword and a range. With this method you could even do dynamic range checking, as you might do when computing a subscript for an array. Something like:
    num index <0,31> ; 5-bit number, or
    num err <-16,15> ; signed 5-bit number
The reason there is chocolate ice cream is because not everybody likes vanilla.

ETA: It might be worth mentioning that "C" was not invented for the purpose of avoiding confusion. The inventors certainly didn't have any.
 

MrChips

Joined Oct 2, 2009
30,714
There is a native ASM construct for forever, repeat, always, exit, etc. on every computer.
It is called JMP, or GOTO.
Just kidding!:p:eek:
 

ApacheKid

Joined Jan 12, 2015
1,533
I don't think any of those are very serious issues. Maybe I'm missing something, and I just don't understand the argument.
  1. If forward declarations are those that occur before an identifier is used, then I don't see that as a major flaw. Gone are the days when there at least was an imperative to compile or assemble in a single pass. I don't think allowing only a single pass through the source is done any more by anybody, but I could be wrong.
  2. C already has the do { ...statements..} while <condition> ; construct. This is effectively the same as repeat { ...} until <condition> ; If you must exit from inside the loop there is the break ; keyword.
  3. I suppose you could substitute the number types with a single type keyword and a range. With this method you could even do dynamic range checking, as you might do when computing a subscript for an array. Something like:
    num index <0,31> ; 5-bit number, or
    num err <-16,15> ; signed 5-bit number
The reason there is chocolate ice cream is because not everybody likes vanilla.

ETA: It might be worth mentioning that "C" was not invented for the purpose of avoiding confusion. The inventors certainly didn't have any.
Its not really an argument. I have a personal interest and experience in programming language design and implementation and as I play around with these MCUs it just strikes me as how C is OK but is not really what we'd design if creating a language primarily for writing code for MCUs.

Individually many of the shortcomings are not serious I agree, but taken together they collectively hinder rather than help the developer - IMHO anyway.

So it might seem like nit picking to some, but really it isn't, I see the language differently perhaps to many here because I've built compilers for languages (and used C to write them) so I kind of look at this differently. There are many details I've not mentioned that when considered, do make a good case for a new language.
 

WBahn

Joined Mar 31, 2012
29,979
So it might seem like nit picking to some, but really it isn't, I see the language differently perhaps to many here because I've built compilers for languages (and used C to write them) so I kind of look at this differently.
I think this is a point worthy of reflection. I'm assuming that your general position is that there are many more modern languages that have more agreeable syntax rules than C. So why did you use C to write your compilers and not a "better" language? It would seem that C offered features that were significant enough that it was worth the pain of using its syntax. That brings up the question of why these "better" languages didn't offer those features and whether they could reasonably be incorporated with that languages "better" syntax rules.
 

ApacheKid

Joined Jan 12, 2015
1,533
I think this is a point worthy of reflection. I'm assuming that your general position is that there are many more modern languages that have more agreeable syntax rules than C. So why did you use C to write your compilers and not a "better" language? It would seem that C offered features that were significant enough that it was worth the pain of using its syntax. That brings up the question of why these "better" languages didn't offer those features and whether they could reasonably be incorporated with that languages "better" syntax rules.
Well a PL/I compiler was developed in the early 1990s, for Windows then later Windows NT. There weren't many options back then and C was well supported by Borland and Microsoft. That's really the gist of that. In fact one motivator for the project was to be able to develop in PL/I on Windows rather than C, as PL/I is a superior language.

This the compiler, it matured but the project never became commercial so its now just sitting there, the parser itself is here.

Finally this is a test source file, testing that we can parse code that uses keywords as identifiers - fully legal in PL/I.

Terms if else then goto allocate while etc are all PL/I keywords, but as that sample shows, code using these as identifiers compiles absolutely fine - keywords are not reserved in PL/I.

Code:
    if if = then then
       else = if - then;
    else
       then = if - else;
The language has no problems with this, the parser has no problems (nobody would intentionally write that but that isn't the point, the point is that a new keyword could be introduced to the language - a word that was never a keyword before - and the parser has no trouble distinguishing a legit use of the keyword from some use where the programmer just happened to have used the word as an identifier).

 
Last edited:

WBahn

Joined Mar 31, 2012
29,979
A simple example of the kind of thing I'd like to see removed when designing a new language, is the need for forward declarations. There is absolutely no good reason for these other than the chosen grammatical rules. It is quite straightforward to design a language front end that doesn't need these.
C99 and C11 have greatly relaxed these requirements so that you can usually declare variables at the point of first use.

I have mixed feelings about this. While it's certainly convenient to declare a new variable at the point you decide to use it, it makes it a lot harder to keep track of what type a given variable is because its declaration can appear (seemingly) anywhere within the code. Whereas having to declare variables before the first line of executable code greatly narrowed the range of code where the declarations could be found. It also makes it so that you can usually see all of the declarations for a given function on one screen of code, which allows us to better see the consider the relationships compared to having to scroll/search all over the place.

When I was first programming in C, I always put the main() at the top and the functions it called below it, so I got used to having to provide forward declarations (i.e., prototypes) for them. This was because I wanted to read down a source code listing and see things from top-level to bottom-detail like a story. At some point, someone pointed out that putting the main() at the bottom and then functions above it in such a way that a function is defined prior to it being called by any other function not only eliminated the need for function prototypes, but also made it so that the compiler could catch and at least give you a warning about any unintended forward references, such as you get with accidental mutual recursion.
 

ApacheKid

Joined Jan 12, 2015
1,533
C99 and C11 have greatly relaxed these requirements so that you can usually declare variables at the point of first use.

I have mixed feelings about this. While it's certainly convenient to declare a new variable at the point you decide to use it, it makes it a lot harder to keep track of what type a given variable is because its declaration can appear (seemingly) anywhere within the code. Whereas having to declare variables before the first line of executable code greatly narrowed the range of code where the declarations could be found. It also makes it so that you can usually see all of the declarations for a given function on one screen of code, which allows us to better see the consider the relationships compared to having to scroll/search all over the place.

When I was first programming in C, I always put the main() at the top and the functions it called below it, so I got used to having to provide forward declarations (i.e., prototypes) for them. This was because I wanted to read down a source code listing and see things from top-level to bottom-detail like a story. At some point, someone pointed out that putting the main() at the bottom and then functions above it in such a way that a function is defined prior to it being called by any other function not only eliminated the need for function prototypes, but also made it so that the compiler could catch and at least give you a warning about any unintended forward references, such as you get with accidental mutual recursion.
I should have stressed, its forward function declarations I was referring to.
 

Papabravo

Joined Feb 24, 2006
21,159
There is no easy way out. You either have to make a forward declaration or you have to have a 2-pass compiler.
A one pass compiler was a big deal in the days when an operator would have to reload a card deck to run a second pass, or worse a tape drive would have to rewind and search for the beginning of the correct source text. That is no longer really a consideration since most compilers can run multiple passes without breaking a sweat. There were systems that would actually prevent you from doing either of those things.
 

nsaspook

Joined Aug 27, 2009
13,086
Well people who see no shortcomings in C are of course going to show little interest in considering improvements, I have no idea what the "majority of embedded programmers" think about this subject, but as someone with experience of developing compilers and parsers and optimizers I do have some insights into the question.
If you think the C language has issues you should at the errata for most popular controllers. :eek:
https://www.st.com/resource/en/erra...29439-line-limitations-stmicroelectronics.pdf
https://www.espressif.com/sites/default/files/documentation/esp32_errata_en.pdf
https://ww1.microchip.com/downloads...l-GPGMCJ-Family-Silicon-Errata-DS80000833.pdf

I'm sure you do but don't think that gives you expertise in areas you have little experience in. New compilers and parsers and optimizers usually introduce a new set of features (bugs) we have to deal with because the writers have little or no experience in the domain they are being used in. This is less true when C compilers and software systems are sponsored by the hardware manufactures as a way to sell product. If the bug(s) is so bad people start to use other devices, it will usually be fixed or at least a temporary workaround found..
 
Last edited:

ApacheKid

Joined Jan 12, 2015
1,533
There is no easy way out. You either have to make a forward declaration or you have to have a 2-pass compiler.
Well the term "one pass" and "two pass" etc are redundant these days, the terms stem from a bygone era where there was so little memory that one needed to re-read source text several times to extract the details one needed.

Today the source is simply consumed by a scanner, then parser and a tree structure is built. Various algorithms then walk the tree and do whatever is needed.

I very much doubt any modern language compiler reads the source text from disk more than once, so the entire forward declaration thing is nothing more than an implementation artifact.

Of course C does declare "prototypes" because this is necessary for defining functions that are not defined in the compilation unit, but that's a different thing altogether.
 

ApacheKid

Joined Jan 12, 2015
1,533
I recall an interesting feature or two found in PL/I too now that I'm looking at all this. These are "controlled storage" and "based storage".

With controlled storage one could allocate an instance of some structure but there was no pointer, the instance was just allocated as if on a stack. Then one could allocate it again and again and the instance was always the most recently allocated instance, when one freed it then the previous allocated instance became the current instance.

With based storage one declared some structure as being "based (some_ptr)" and could then refer to the structure without any need for an explicit pointer, the pointer was implicit in the based declaration.

So if a pseudo-C language had this we could write:

Code:
pointer master_ptr;

typedef struct
{


} MasterLayout based (master_ptr);
Then one can just refer to MasterLayout in the code and it would be referencing data based on wherever the pointer happened to point.

There was also "defined" storage where some declared thing was overlayed on some other, again these features reduce the need to explicitly refer to pointers in the code, and the code was very clean and readable.

These are the kinds of ideas that I think could be useful for MCU programming, hell for all I know PL/I might be not a bad MCU language itself, it was the first high level language ever used to write an OS after all...
 
Last edited:

ApacheKid

Joined Jan 12, 2015
1,533
Overall I think Rust is probably the best language to use for embedded these days if one has a free choice in the matter. Although the tooling and toolchains seem be not as solid as for C. Also Visual GDB which I like to use, doesn't really yet do anything for a Rust world.
 

nsaspook

Joined Aug 27, 2009
13,086
Overall I think Rust is probably the best language to use for embedded these days if one has a free choice in the matter. Although the tooling and toolchains seem be not as solid as for C. Also Visual GDB which I like to use, doesn't really yet do anything for a Rust world.
I've looked at Rust and like it. Learning Rust will make you a better C programmer. ;) At this point it's IMO still a great solution looking for a problem people can't manage by other means easily.

Identifying, at the compile stage
Memory bugs
Concurrency bugs
Consequences of Undefined Behavior's
is a nice want but they also can be identified externally to the language compile stage in languages like C while being avoided by the experienced programmer in the first place.

I don't see 'Safe Rust' making much headway in the small (with billions of devices) embedded controller domain where you don't have the attacks surface that security nightmare IoT devices have and traditional C/C++ tool chains, SDKs are vendor supplied by the users demand to sell product by staff with other things to do than fight a compiler after dragging in the whole "ecosystemic language" pipeline of Rust infrastructure. The prime business adoption question is 'Will It Make Us Money?'
Sadly most of the surface attacks on those IoT devices can't be cured by source programming the device in Rust because the basic security protocols on most are inherently broken.

Unsafe Rust, "a subset of the language that allows programmers to bypass certain—but not all—static safety guarantees" is a loophole the size of Texas to let you write a couple bits to a memory-mapped peripheral register.
https://docs.rust-embedded.org/book/peripherals/a-first-attempt.html

Only time will tell how it shakes-out. Practical considerations will make or break Rust, not a theoretical advantage over X programming language.
 
Last edited:

ApacheKid

Joined Jan 12, 2015
1,533
I've looked at Rust and like it. Learning Rust will make you a better C programmer. ;) At this point it's IMO still a great solution looking for a problem people can't manage by other means easily.

Identifying, at the compile stage

  • Memory bugs
  • Concurrency bugs
  • Consequences of Undefined Behavior's


is a nice want but they also can be identified externally to the language compile stage in languages like C while being avoided by the experienced programmer in the first place.
I'm not clear on what you're saying here Spook.

C lets us write both of these, and they are both legal C:

Code:
if ( i == 10 ) {}

if ( i = 10 ) {}
This (and other examples) exists because C regards assignments as having a value, an assignment can be an operand in an expression! The language allows that and our systems pay the price for such sloppiness.


I don't see 'Safe Rust' making much headway in the small (with billions of devices) embedded controller domain where you don't have the attacks surface that security nightmare IoT devices have and traditional C/C++ tool chains, SDKs are vendor supplied by the users demand to sell product by staff with other things to do than fight a compiler after dragging in the whole "ecosystemic language" pipeline of Rust infrastructure.
I can't comment because I'm unfamiliar with Rust and even embedded is pretty new to me. The practicalities of adopting a new language are of course a big consideration but as the saying goes "no pain, no gain".

The prime business adoption question is 'Will It Make Us Money?'
Sadly most of the surface attacks on those IoT devices can't be cured by source programming the device in Rust because the basic security protocols on most are inherently broken.
That may well be true but I was not arguing they be cured, only that C is a poor language and that is due partly to it having a poor grammar, that's all I've argued here.

Unsafe Rust, "a subset of the language that allows programmers to bypass certain—but not all—static safety guarantees" is a loophole the size of Texas to let you write a couple bits to a memory-mapped peripheral register.
https://docs.rust-embedded.org/book/peripherals/a-first-attempt.html

Only time will tell how it shakes-out. Practical considerations will make or break Rust, not a theoretical advantage over X programming language.
Well C of course has no "safe" equivalent, so comparing them that way doesn't help the case for C very much! I mean C is "unsafe" period.

Rust's "unsafe" is an option, it is either used or not. There is a lot out there about Rust's safe/unsafe capabilities. Moving from C to something else is of course a cost, but not doing so also carries a cost in terms of time spent dealing with unexpected failures, time spent testing, time spent debugging and so on.
 

nsaspook

Joined Aug 27, 2009
13,086
I'm not clear on what you're saying here Spook.
...
Well C of course has no "safe" equivalent, so comparing them that way doesn't help the case for C very much! I mean C is "unsafe" period.

Rust's "unsafe" is an option, it is either used or not. There is a lot out there about Rust's safe/unsafe capabilities. Moving from C to something else is of course a cost, but not doing so also carries a cost in terms of time spent dealing with unexpected failures, time spent testing, time spent debugging and so on.
I'm saying that these class of memory safety bugs are the ones a language like Rust tries to prevent in compile-time instead of run-time.
https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
Unsafe Superpowers
To switch to unsafe Rust, use the unsafe keyword and then start a new block that holds the unsafe code. You can take five actions in unsafe Rust that you can’t in safe Rust, which we call unsafe superpowers. Those superpowers include the ability to:

  • Dereference a raw pointer
  • Call an unsafe function or method
  • Access or modify a mutable static variable
  • Implement an unsafe trait
  • Access fields of unions
By opting out of having Rust enforce these guarantees, you can give up guaranteed safety in exchange for greater performance or the ability to interface with another language or hardware where Rust’s guarantees don’t apply.
Both Rust and C are "unsafe" in the same mine field (dealing with real world hardware will always be unsafe in the programming sense) of those types of bugs and equivalently 'safe' elsewhere.
The difference is a electronic mine detector with Rust or a knife and hands probing the soil ahead with C.
1666367624429.png1666368128516.png

People with experience tend not to put full trust in the detector to make it 'safe'.
 

ApacheKid

Joined Jan 12, 2015
1,533
I'm saying that these class of memory safety bugs are the ones a language like Rust tries to prevent in compile-time instead of run-time.
https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html

Both Rust and C are "unsafe" in the same mine field (dealing with real world hardware will always be unsafe in the programming sense) of those types of bugs and equivalently 'safe' elsewhere.
The difference is a electronic mine detector with Rust or a knife and hands probing the soil ahead with C.
View attachment 279022View attachment 279023

People with experience tend not to put full trust in the detector to make it 'safe'.
C cannot protect a programmer from making certain mistakes whereas Rust does. Yes all such languages work at an unsafe level under the hood where machine instructions are used but Rust can and does strictly and explicitly isolate unsafe code.

Have a read of this: Computer Scientist proves safety claims of the programming language Rust

That's proof in the mathematical sense, as in we can prove the square root of two is irrational, that kind of proof.

"We were able to verify the safety of Rust's type system and thus show how Rust automatically and reliably prevents entire classes of programming errors," says Ralf Jung. In doing so, he also successfully addressed a special aspect of the programming language: "The so-called 'type safety' goes hand in hand with the fact that Rust imposes restrictions on the programmer and does not allow everything that the programmer wants to do.
So if the team are all writing safe Rust code these kinds of errors simply cannot happen, ever, ever, ever - that is a huge improvement in quality.
 
Top