What did you learn or decided to figure out recently? by mostly nonembedded dev - Page 1
Forum section: electronics / general

author: unitedatoms
author_rank: Frequent Contributor
timestamp: July 30, 2020, 10:14:32 pm
content: I am curious what embedded devs have to learn comparing to non-embedded app devs. I learned something about C, ARM Cortex and toolchains which I never had time before. I decided to look at what I dont know for certain and study it more thoroughly.So here is list of what I think I learned. Things I was not certain about until I got time and finally studied.1. The function named SystemInit() in System_<device>.c is called before C-Runtime library clears .bss. So the static vars in SystemInit() are nullified. Surprise.2. I can use const X = something(a,b,c) inside functions3. My target is actually is little endianess kind. Never thought about that until I froze the target with wrong compiler setting4. The scatter files and linking script files are the same thing with different names5. The flashing algorithm is actually in near the top area of flash ROM. I was curious and looked at it disassembly in debugger. I wonder how they flash this code in the fab ?6. The SysTick hardware is actually running at high MHz CPU speed. I can possibly make related routine occupy 99.99% of CPU time if not careful7. Some manufacturer's code examples are useless if you try using some pins as analog inputs. High isolating decoupling from rest of digital lines inside CPU is impossible.8. Manufacturers have bits of their own private flash which they write during fab characterization. For consts related to RC oscillator and speed of flash memory.9. C-Runtime semishosting is not C-Runtime retargeting, though one is relevant to other10. The API the C-Runtime depends on is not same thing as other thing. (forgot what other thing is named. It is the thing about floating point functions, memory moves and access to unaligned data)11. The printf breaks for floats if you use high O3 optimization with link-optimization on. I dont know why ?12. There is no way to find explanation of how compiler checkboxes and flags are represented in #defines inside the code so I could catch an #error for unexpected flags. Basically I wish to enforce all the flags to be as I expected.13. C functions can actually return Struct and compiler allocates space on a caller side.14. You can boild down from thousands of compiler's warnings to zero if you want.15. The (SomeType* const MyParam) qualifier is now my favorite to use. And adding const everywhere is great.16. The asm("BKPT 0") actually halts target when debugger is not connected17. Int64 type seems to be not atomic on ARM 32 bit chips. It never was.18. There is no dedicated float math expression for round(), the parameter is always treated as double. Even if implementation branches with sizeof() logic.19. There is no way to I know of to nest enum values inside some sort of namespace. C has no namespaces....I had to write it down or else it all will fade from memory over time.

author: thm_w
author_rank: Super Contributor
timestamp: July 30, 2020, 10:42:26 pm
content: Quote from: unitedatoms on July 30, 2020, 10:14:32 pmI was going to say its hardcoded, but sounds like it is flashed as you say (for STM32):Quotehttps://www.st.com/resource/en/application_note/cd00167594-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdfThey could be using silicon test pads not available on the final packaged IC to load that data (along with CAL stuff).Sometimes you'll see a 'test' pin on a chip that are actually bonded out, then in the datasheet "always to be held low" or similar.

author: langwadt
author_rank: Super Contributor
timestamp: July 30, 2020, 10:50:02 pm
content: which tool chain?1) in the standard AC6 project memory is initialized in asm before anything else5) probably load code in ram and have some magic to enable access to the special areas11) have you tried -u_printf_float ?18) roundf() ?

author: unitedatoms
author_rank: Frequent Contributor
timestamp: July 30, 2020, 10:58:43 pm
content: Quote from: langwadt on July 30, 2020, 10:50:02 pmARM uVision with their flavour of clang. CMSIS DAP adapter from Adafruit and W7500P target. Their flash algo at source level is just pointers to fixed addresses.roundf(). I must look at it. Thank you

author: langwadt
author_rank: Super Contributor
timestamp: July 30, 2020, 11:09:20 pm
content: Quote from: unitedatoms on July 30, 2020, 10:58:43 pmin addition to the single precision variants of floating point operationsif you have single point fpu and/or don't want doubles, you might want to look at-Wdouble-promotion -Wfloat-conversion -fsingle-precision-constant

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 31, 2020, 12:01:47 am
content: Quote from: unitedatoms on July 30, 2020, 10:14:32 pmYep, usually there's something in the datasheet about avoiding using GPIOs on the same IO bank as the analog mux.Doesn't hurt to read the errata, either. Some notorious Microchip PICs you'd think were written in PHP!QuoteYup, can be handy for storing a MAC address (or reading, or calculating from) for example. Or initializing RNG. Hmm...20. There are no random numbers. Well, you may get lucky (ha) with some peripherals. Two typical sources of entropy: LSBs from the ADC, and timing error of a counter running from the RC timer.Keep this in mind if you ever find yourself, for example, running a network protocol that uses random numbers to attempt to resolve conflicts!QuoteDepends, I guess? Usually the functions are linked in from a library. I suppose dynamic libraries could be used on some platforms (anything that can execute from RAM). Would be a bit strange to do.Quoteprintf is big anyway.21. Don't use printf. There's a lighter weight, safer function you can use.22. Floats are slow and bulky on most MCUs. If you have FP extensions, fantastic; if not, try to work in fixed point.QuoteThis depends on the ABI (application binary interface), I think? For example on avr-gcc (with which I am familiar), there are 32 8-bit registers. A few have caller priority (callee has to save on stack before using them). Most are used, in order, to pass parameters in and out. Variadic functions are always passed on the stack (the number of parameters being passed in register).I think ARM is similar in that most things are passed in register, but there are fewer registers (but much wider, of course); so you can run through them pretty quickly and have to pass parameters (including structs) in stack.23. When optimizing inner loops, watch out for how you're passing data -- if the compiler creates a stack frame, it can eat up a lot of cycles. Prefer smaller operands that fit in registers, and avoid memory and stack.Again, depends on platform; it's probably pretty trivial on ARM, but it's rather obnoxious on AVR (read the two stack registers, store them; disable interrupts and add the offset; load the stack registers; and popping and reloading at end). x86 is kind of inbetween (good old PUSH BP; MOV BP, SP; ADD BP, stacksize), though it's probably even easier than that these days (ENTER and LEAVE, or whatever the x64 equivalent is -- not to mention the oodles of optimizations on them, that make the instruction and cycle counts nearly irrelevant!).QuoteRight, lots of atomic stuff you take for granted in a higher level language, is out the door -- got to use the library constructs every time! And don't forgetvolatile.QuoteHeh, yeah, the C version of that is to prefix everything. Instead of classes, you have compilation units, with public (ordinary) and private (static) fields (globals).Lots of platforms have C++ support -- even the humble AVR for example, which, the infamous toy Arduino environment actually is, so you can do some quite powerful things with it, if you don't mind working around the janky libraries it uses. (You can of course write on base avr-libc as you wish!)Tim

author: greenpossum
author_rank: Frequent Contributor
timestamp: July 31, 2020, 12:32:24 am
content: QuoteYes, this has been in C for a long time. I remember that the manuals for the C compiler in V7 Unix mention support for functions accepting and returning structs. Wasn't implemented everywhere though.

author: westfw
author_rank: Super Contributor
timestamp: July 31, 2020, 02:26:56 am
content: QuoteCan you use the old " -dM -E - </dev/null" hack, possibly after cut&pasting the compile command to a shell window? That's what I do on other platforms. There is, alas, a lot of other stuff in there as well, so judicious use of grep and or an editor search function is called for.QuoteThis should depend on how the debug system is enabled, and whether you have the debug exception vector filled in.ARMv7m Architecture Reference Manual:Quote

author: westfw
author_rank: Super Contributor
timestamp: July 31, 2020, 02:34:01 am
content: For example:arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -c -g -Os -std=gnu++11 -nostdlib -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mfloat-abi=hard-dM -E - </dev/null| grep ARM#define __ARM_SIZEOF_WCHAR_T 4#define __ARM_FEATURE_SAT 1#define __ARMEL__ 1#define __ARM_FEATURE_UNALIGNED 1#define __ARM_FEATURE_IDIV 1#define __ARM_FP 4#define __ARM_ASM_SYNTAX_UNIFIED__ 1#define __ARM_SIZEOF_MINIMAL_ENUM 1#define __ARM_PCS_VFP 1#define __ARM_FEATURE_LDREX 7#define __ARM_FEATURE_QBIT 1#define __VERSION__ "7.2.1 20170904 (release) [ARM/embedded-7-branch revision 255204]"#define __ARM_FEATURE_FMA 1#define __ARM_ARCH_PROFILE 77#define __ARM_32BIT_STATE 1#define __ARM_FEATURE_CLZ 1#define __ARM_ARCH_ISA_THUMB 2#define __ARM_ARCH 7#define __ARM_ARCH_7EM__ 1#define __ARM_FEATURE_SIMD32 1#define __ARM_FEATURE_COPROC 15#define __ARM_ARCH_EXT_IDIV__ 1#define __ARM_EABI__ 1#define __ARM_FEATURE_DSP 1

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 31, 2020, 06:21:14 am
content: That you can writeN-bitK-fractional bit fixed-point (QN-1.K)round()using exact-width integer typeintN_t qas(q + (1<<(K-1)) - ((q<0)<<K)) / (1<<K)or, if you use GCC, as(q + (1<<(K-1)) - !!(q&(1<<(N-1)))) >>Kalthough on some architectures(q + F[!(q&(1<<(N-1)))]) >>KwithF = [(1<<(K-1))-1, (1<<(K-1)) ]can be preferable. Of course, on Cortex-M's, this is basically trivial (and silly fast) using GCC extended inline assembly, and opens up a way to write a nice,precisefixed-point C++ library for microcontrollers, with optional architecture-specific inline assembly variants.Of course you can use approximations, but the above is more precise thanroundf().And that the above are hard to transcribe correctly into a forum post.

author: newbrain
author_rank: Super Contributor
timestamp: July 31, 2020, 08:43:15 am
content: Quote from: unitedatoms on July 30, 2020, 10:14:32 pm1. I think you mean arenotnullified (or initialized in any way), so part of the standard C semantics does not hold true. Tread carefully in those perilous lands.2. Standard C, why is it surprising?10. ?11. printf from which library?13. Standard C, how is this surprising? Efficiency may vary, of course...14. Use of -Wall and -Wextra (for GCC) is always good practice. That's no lint, but a good tool nonetheless.15. In C const is useful to signal intent to the compiler, and get more feedback from it (in C++, there's something more/different), still I see no difference between embedded and hosted, here?17. Until C11 standard, there was no guarantee of atomicity for any type except sig_atomic_t. In practice, the "natural" integer is often atomic for reads and writes, but one cannot rely on it. This holds also for a hosted environment, so no surprise unless one was writing only strictly single-threaded code.18.float roundf(float)is a standard function. Remember that floating point literals are double unless suffixed withf(the compiler avoids useless conversion,usually).19. Yes, another difference between C and C++, where enum is an actual type, rather than a fancy integer constant alias. Still, what's the embedded specific learning point?20. (from T3sl4co1l) Many MCUs have an HW RNG, e.g.STM32s, now, that they are adequate for a specific purpose I cannot vouch.21. I found thissmall printf implementation, and I quite like it (there might be better ones, but this is complete/small/correct/fast enough, easily integrated and MIT licensed).

author: lucazader
author_rank: Regular Contributor
timestamp: July 31, 2020, 09:14:52 am
content: The small printf library that newbrain linked is indeed very useful. nice and compact and straight forward to use!One thing I recently learnt is that Cpp17 depreciated the register keyword.So this results in lots of warnings in vendor supplied libraries when you try to use them in conjunction with c++ code.

author: newbrain
author_rank: Super Contributor
timestamp: July 31, 2020, 09:37:34 am
content: Quote from: lucazader on July 31, 2020, 09:14:52 amIt's more than that.registeris deprecated at least from C++11, now C++17 makes using it as a storage class specifier an actual error, though its use is still reserved (as a keyword) for future uses.A conforming compiler is forced to emit a diagnostic for this kind of error, but can still proceed in the translation (IIRC).From C++17 standard:QuoteQuoteFrom C++11 standard:QuoteI think it's a good choice. It has been a pointless keyword since long time.

author: unitedatoms
author_rank: Frequent Contributor
timestamp: July 31, 2020, 10:14:48 am
content: Quote from: newbrain on July 31, 2020, 08:43:15 amRight not nullified to begin with. And after you done computing them they are nullified again.QuoteI am most of my time in C#/Windows on desktop. When in embedded C I just follow my intuition and random examples, never knew C by the book. This feature was never in other people sources I studied. So somehow I think about this being my own find.QuoteI was for some reason trying to see what is below C Runtime. Still not sure what this ARM documents are for: "C Library ABI for the ARM Architecture" vs "Run-time ABI for the ARM Architecture".QuoteARM Keil clang MicrolibQuote13. Well. I was not sure about it being possible with no heap/garbage collection. Only after listening to Embedded FM podcast I got a clear explanation.18. nice19. My specifics is that I use C only on embedded as new language after decades of C# and Java on PC.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 31, 2020, 10:56:01 am
content: Recently as in a few years ago, but therestrictkeyword. Use it if you handle large arrays of data in processing functions to enable compiler optimizations which you wouldthinkare totally trivial, but aren't because you are not thinking about the strict aliasing rule of C, thinking it's justobviousthat the pointers do not overlap.

author: newbrain
author_rank: Super Contributor
timestamp: July 31, 2020, 10:58:44 am
content: Quote from: unitedatoms on July 31, 2020, 10:14:48 amOh, wow!Then kudos for taking this endeavour, in this light I understand some of your discoveries can be pretty disconcerting.I think you'll have to "unlearn" a lot, C# is a nice language (fells like 'Java done right' to me) I'd like to know more: its level of abstraction is quite different from C - especially so going to an embedded platform at the same time.QuoteBe careful to rely on intuition, C is full of nasty little traps even for not-so-young players (more so for players of an almost-similar-but-quite-different instrument as C#).Eh, I'm quite biased with referring to the standard, and even coding for embedded systems one can go "by the book" for 99% of the code when on a modern platform.Quote from: Siwastaja on July 31, 2020, 10:56:01 amI think this really brought C much closer to Fortran's performance.When I started working (let's say C89 was out...barely), I was really surprised on seeing the difference in speed of the "same" code in C and Fortran, so much that I switched to Fortran for the core routines of a simulator I had to write.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 31, 2020, 11:04:04 am
content: Also the danger of working by 'net examples of C is that many of those are based on principles from the very early C from the 1970's, yes, really; because the same examples just hang around for eternity.Many examples do not take advantage of the feature set of modern C,eventhat of C89, let alone C99.Unlike C++, C being a simple language with quite modest feature set, it makes sense to actuallyusemost of the language, really!Returning structs being one example. Even assigning one struct to another, not everyone do it, instead using memcpy calls, usually with improper sizeofs, which is more error prone and more complex for a beginner, for no reason. Fixing such practices make your code higher-level instantly with no performance penalty, usually.

author: Renate
author_rank: Super Contributor
timestamp: August 01, 2020, 12:49:42 pm
content: Quote from: unitedatoms on July 30, 2020, 10:14:32 pmYes, this is not utilized a lot although it can be handy for returning two (or more) values in an ad-hoc structure.The flip side is argument passing structures in place.It might make some sense for small structures but it will make people do double takes when you put it in your API.Quote

author: unitedatoms
author_rank: Frequent Contributor
timestamp: August 01, 2020, 01:29:14 pm
content: If microblogging is OK, I will add. I mean today is another today. And the difficulties are everincreasing.I tried to bring some order and classification to see how many languages should I know for embedded development. Basically 4-5 more than for some single language desktop dev.It boils down to 9 languages in order of my personal incomprehension.Code:language 1: Programming language C/C++ Example: "void main();" Artifacts: some *.c fileslanguage 2: Assembly Example: ".cpu cortex-m7" Artifacts: startup_<blabla>.sproject content 3: .project, .xml, makefile Example: <project name=" .. Artifacts: single filenamings for tools locations 4: UNC Example: C:\GNU1234\bin\.. Artifacts: IDE guts, registry, ENV, PATH, everywhere allovertranslation tool options 5: CLI of compiler Example: +O3 .. and 2000+ more Artifacts: project files, makefiles, basically everywheretranslation tool options 6: CLI of assembler Example: -target=arm-none-... Artifacts: closer to project files, makefiles, and in IDE.exelinking tool options and inputs 7: CLI and script for linker Example: MEMORY { .RAM1 ... Artifacts: in IDE.EXE, platformio.exe and in generated .sctnamings for flashing/debug tools 8: URL Example: localhost:5555 Artifacts: on tips of your fingers, in IDE.exe registryscripting languages for debugging 9: GDB Example: halt Artifacts: on tips of needles under nails of your fingers

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 01, 2020, 04:23:18 pm
content: Quote from: Siwastaja on July 31, 2020, 11:04:04 amI've found the same applies to other languages as well, such as VHDL. Still a lot of code examples around that do not even take advantage of VHDL-93.Quote from: Siwastaja on July 31, 2020, 11:04:04 amAssignment of structs, of course. Returning structs - I tend to avoid it for large structs for performance reasons, but for smaller structs, that's fine. It can make your code look more "functional" and avoid fiddling with pointers all over the place.That said, I admit I don't remember much of pre-ISO C, so I'm not sure, but couldn't you already assign and return structs in pre-ISO C?

author: Siwastaja
author_rank: Super Contributor
timestamp: August 01, 2020, 05:09:41 pm
content: Quote from: SiliconWizard on August 01, 2020, 04:23:18 pmI return structs nowadays in inline functions when performance matters - now the compiler should be able to optimize the usage as a whole. Some coordinate transformation would be a typical example, say, translating and rotating a million of points. Per point, it's all trivial math, maybe ten arithmetical operations, where even a simple function call overhead is too much, but that does benefit from abstracting low-level access away, type checking of function arguments is a plus.Or, sometimes performance doesn't matter, or the struct is small enough not to matter. Two ints of 4 bytes? Not any larger than returning a pointer of 8 bytes.Or the struct is something like two integers that work as a reference to something else, like struct {int vao; int vbo; int n_triangles;} in an OpenGL meshing function; or file descriptors and a control parameter. Because this data is already a reference, although not technically a pointer, having another layer of pointer would not make it much smaller, or add any clarity, quite the opposite.QuoteI think too many C writers (probably myself included) have theoppositeproblem of many C++ writers. Whereas the C++ horror story is someone who tries to use every conceivable modern-day C++ feature in a trivial program, making the code impossible to understand by anyone else, the C horror writer is someone who does not know about or refuses to use most actually useful C features, thinking they do not exist. I don't know, but I would assume returning and assigning structs has been impossible atsomepoint of history, otherwise it's weird why you see memcpy'ing structs to each other in code, still. I have done that, too, copying the habit from somewhere!

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 01, 2020, 06:13:41 pm
content: Quote from: Siwastaja on August 01, 2020, 05:09:41 pmWell, yes. That details what I briefly said actually.I consider your examples "small structs". By large structs, I would imply structs that are hundreds or thousands of bytes, which is not uncommon either. For "large" structs, it's often impossible to really optimize anything. But coordinates are usually a good example for doing this.Quote from: Siwastaja on August 01, 2020, 05:09:41 pmI don't know where that comes from, so that's quite possible. But that may just come from what I said above - most C courses out there used to tell you to avoid passing around structs directly (and probably still do), so people just tend to stick to that without giving it a second thought. But generally speaking, one thing I noticed over the years is that C users, on average, tend never to really LEARN the language - they just know the basics and are content with that (funnily enough, that's the kind of developers - even seasoned ones - you'll find on programming forums asking sometimes for very basic questions on C.) This can be seen very often, and we have examples of that on this forum - even in this thread!Yes, there is a definite trend here depending on the language. To sum it up very roughly, it looks like users of "low-level" languages tend to be very conservative - and tend not to actually learn the full language, let alone learn evolutions of it, even when said evolutions are pretty old already, so you see this behavior among C, VHDL, Verilog users for instance. And users of "high-level" languages tend to do the exact opposite: always want to use the latest features - you'll see that with C++ of course, but also with Java. Ironically, this behavior seems to be backed up by the job market - I've seen quite a few job interviews for C++ developers that would specifically check that candidates were proficient with specific C++14 or C++17 features, for instance. Of course, that's usually because the team they are hiring for uses this particular C++ version, but that just reinforces the trend.I guess with simple languages, many people assume those are so simple that there is nothing much to learn. They don't learn more than the basics because they *can* get away with it - somewhat. With more complex languages, they just don't have a choice, so that forces people to learn a lot more. And the more you have to learn, the more you feel "empowered", I suppose, so that becomes an ever-moving target.

author: mikerj
author_rank: Super Contributor
timestamp: August 01, 2020, 07:05:45 pm
content: I just learned (the hard way) the Keil/Clang ARM linker aligns executable sections to the nearest 2048 byte boundary by default, causing huge chunks of memory to be eaten up.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 01, 2020, 08:04:21 pm
content: Quote from: Siwastaja on July 31, 2020, 11:04:04 amDon't get me started aboutopendir()/readdir()/closedir()examples in Linux and POSIX environments.There is a reasonftw(),nftw(), andglob()are a part of POSIX, and why both Linux and BSDs and many Unix variants implementedfts_open()/fts_read()/fts_children()/fts_set()/fts_close(). While all implementations are not equal, at least these aresupposedto handle the cases where directories and/or files are renamed or moved during directory traversal; open-coding the naïve implementation as an example is pretty horrible, in my opinion. ("Here is how you drive a car with triangular wheels, my protegé.")As to structures, it is useful to note that on Linux in x86-64, a structure with two doubles or four floats will be returned in xmm0 and xmm1 registers; a structure with one double and one long will be returned in xmm0 and eax registers; and a structure with two floats followed by one long will be returned in xmm0 and eax registers. When inlined, structures passed (by value) and returned, with float or double parameters, are kept in registers (unless register pressure or compiler stupidity does it otherwise). This does make passing structures – as opposed to passing pointers to structures – rather important in many high-performance computations involving small structures on Linux on x86-64, without making the code any less portable.

author: westfw
author_rank: Super Contributor
timestamp: August 01, 2020, 11:42:27 pm
content: QuoteYou have only to look for code that has its own equivalent of uint8_t and friends. Shockingly common.And even for the code that uses uint8_t, I suspect that uint_least8_t ought to be a lot more common, especially in these days of migration-to and compatibility-with 32bit embedded processors.(although, people place a lot of faith in compilers these days. It's not obvious that the compilers that people actually end up using deserve quite that much faith. SDCC? Off-brand PIC compilers?

author: 0db
author_rank: Frequent Contributor
timestamp: August 02, 2020, 12:25:54 am
content: Found a bug in a colleague's source.if (var1) then if (var2) then do1(); else do2();gives rise to an ambiguity in interpretation.And a simple bracket-block solve it.But which one is correct?a) if (var1) then { if (var2) then do1(); } else do2();b) if (var1) then { if (var2) then do1(); else do2(); }Solved by a couple of empirical speculative attempts to "understand" what the original author wanted to do with the code.

author: newbrain
author_rank: Super Contributor
timestamp: August 02, 2020, 01:54:25 pm
content: Quote from: 0db on August 02, 2020, 12:25:54 amDon't leave us with a cliffhanger!Which language is that?What is the language interpretation?At least in C the meaning is completely unambiguous.I would not like to encounter code like that, so I don't write it.But the meaning is not ambiguous in any way. Any editor can indent it to make it, if not nice, at least readable.Code:if (var1)if (var2)do1();elsedo2();At least in Forth, there's no doubt. It's confusing no matter what.Code:var1 IF var2 IF do1 ELSE do2 THEN THENvar1 IF var2 IF do1 THEN ELSE do2 THEN

author: 0db
author_rank: Frequent Contributor
timestamp: August 02, 2020, 03:56:22 pm
content: Quote from: newbrain on August 02, 2020, 01:54:25 pmC language.Quote from: newbrain on August 02, 2020, 01:54:25 pmI don't know, ambiguous grammar is a "context-free grammar" for which there exists a string that can have more than one leftmost derivation or parse tree, while an "unambiguous grammar" is a context-free grammar for which every valid string has a unique leftmost derivation or parse tree.It looks the "selection-statement = IF ( expression ) statement ELSE statement" definition in the C grammar could ambiguously be parsed because "statement" contains the "selection-statement" itself, although, it also looks like in C the first tree is "usually" chosen by associating the else with the nearest if.I don't know, and I am not sureBut facts are that the above C lines were "ambiguous" because when tried on two different C compilers exposed two different behaviors, until a couple of block-brackets finally fixed the ambiguity.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 02, 2020, 04:10:56 pm
content: Quote from: 0db on August 02, 2020, 03:56:22 pmWhereas I do agree this is REALLY bad style, newbrain is right in saying this is unambiguous in C.The standard states: "An else is associated with the lexically nearest preceding if that is allowed by the syntax.", so newbrain's interpretation is right.Of course, yes this is definitely bad style - using indentation would be a bare minimum to make this remotely readable, and using braces when dealing with nested if's is a must IMHO for readability reasons.

author: newbrain
author_rank: Super Contributor
timestamp: August 02, 2020, 04:22:30 pm
content: Quote from: 0db on August 02, 2020, 03:56:22 pmSorry, the "then"s throw my parser off.I thought it was some scripting language (PowerShell like).Since SiliconWizard spared me the burden of quoting the content, I'll fill in the chapter and verse: 6.8.4.1, paragraph 3.It's the same in C99, C11 and C18, while in C89 it was phrased slightly different:QuoteQuote from: 0db on August 02, 2020, 03:56:22 pmEither this wasmanyyears ago (let's say before C89*) or at least one of the two compilers was shit: even if set in a non-conforming mode (the default for many compilers!) it would be idiotic to change a semantic that's 'always' been there.The other might have also been crap for other reasons, of course.*Though the C89 Rationale does not mention any change in this respect vs. 'K&R' C.

author: Renate
author_rank: Super Contributor
timestamp: August 02, 2020, 04:25:41 pm
content: Some places are strict and insist that all ifs and elses must use braces.I find that a bit overblown in simple else-less ifs.Code:if (verbose) printf("Found %u\n", count);Although I tend to use braces in an both halves of an if/else if one of the branches uses braces.And I won't give an example because then we'd just squabble over where the braces fall!

author: 0db
author_rank: Frequent Contributor
timestamp: August 02, 2020, 04:32:09 pm
content: Quote from: SiliconWizard on August 02, 2020, 04:10:56 pmlesson learned #1: braces unconditionally reduce the resulting clutterLesson learned #2: indentation is for sure a premium, but it's completely vanished when the code passes through code beautifiers, and I wouldn't rely on it.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 02, 2020, 04:39:13 pm
content: Quote from: Renate on August 02, 2020, 04:25:41 pmYes and same.Cases for which I use braces:- Nested if, or another flow control construct: so for instance: "if () { if () ... }", or "if () { while () ... }".- As you do, if the "then" or "else" clause uses braces, I put braces for both clauses.As for indentation, I ALWAYS indent "then" and "else" clauses. Never any "if" on a single line, even when the "then" and/or "else" clause is very short/simple.As a "beginner" in C, I used to do otherwise. I often wrote "if"'s on a single line, would also avoid braces.One thing I was often writing was things like this: "for () if() ... else ...". Although it looks less "ambiguous" than nested "if"'s, I consider this bad style these days and would never write code like this anymore. In such a case, I also use braces and indentation.

author: newbrain
author_rank: Super Contributor
timestamp: August 02, 2020, 04:42:41 pm
content: Quote from: 0db on August 02, 2020, 04:32:09 pmI agree, I would also not rely on any code beautifier that gets this wrong.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 02, 2020, 04:54:08 pm
content: Quote from: newbrain on August 02, 2020, 04:42:41 pmAhah, exactly. I guess 0db meant they wouldn't rely on indentation for this reason, but I agree with you: any "code beautifier" that doesn't indent code properly according to the C grammar should definitely not be used. Worse yet, a code beautifier that would actually *remove* indentation when the original indentation makes sense would be a code "uglifier".I don't like code beautifiers anyway. The rules they use may not fit your own rules, and very few implement rules that actually are unquestionably good style. Code style is an integral part of writing code IMHO. I don't want tools that mess with this. And if used to make up for the bad style some developers use in a given team, then I would rather get those people to use a better style than use a code beautifier. But I do admit some developers have a hard time using a good and *consistent* style. I just do think this usually reveals a deeper problem that any automated tool won't solve. Just my 2 cents.

author: 0db
author_rank: Frequent Contributor
timestamp: August 02, 2020, 05:09:00 pm
content: Just found there were also problems with the C optimizer.while (True) { if (var) { do1(); } else { do2() }; }got translated at the machine level into something likeif (var) { while (True) do1() } else { while (True) do2() }since "var" is expected to be always True then do2() is expected to be dead code, kept in the source for documenting reasons, and I was seriously surprised to see it running.Looking at the assembly code revealed the mess made by the optimizer, while turning off optimization produces the expected behavior with do1() running!Lesson learned #3: don't trust the compiler, and first compile with optimization off.But what I do really think? At this point I seriously think that this proprietary C/89 compiler for SuperHitachi must be rather bugged to have all of these weird behaviors, and I am going to replace it with a Gcc-SH or LLVM.

author: newbrain
author_rank: Super Contributor
timestamp: August 02, 2020, 05:42:23 pm
content: That, under some conditions, is a good optimization.It saves a number (infinite) evaluations of var.Roughly speaking, 'var' must be side effects free if it is an expression or not volatile if a simple variable.EtA: and, importantly, the compiler must know that do1() and do2() do not change var.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 02, 2020, 05:56:23 pm
content: Quote from: 0db on August 02, 2020, 05:09:00 pmWell, it looks strictly equivalent to the original code to me? But as newbrain said, factoring out the "if (var)" test is an obvious optimization here. It's equivalent, but more efficient.As long, of course, as "var" doesn't change values while this piece of code executes.If OTOH "var" can change values during the loops, then I'd agree this optimization is wrong. But you seem to be certain "var" is always true here, so the optimization would be correct.Quote from: 0db on August 02, 2020, 05:09:00 pmIf the "else" part is executed, then obviously 'var' is false at some point - at least the translated code you mention here can't mean otherwise. You should probably use a debugger or something to figure out WHY do2() is executed. A conditional breakpoint on "var" becoming false would be interesting here. You might have assumed it's always true, when it may not be.If the behavior is different between optimized and non-optimized code, then I would suspect something wrong about how "var" is handled, but not about this piece of code, which again, unless I missed something, is strictly equivalent to the original code.The fact the compiler doesn't strip off the "else" clause seems consistent with the fact you see it running at some point. Likely the optimizer didn't detect that "var" was always true, and the execution seems to be consistent with that. It isn't.We'd probably need to see the rest of the code, because this short piece doesn't tell us anything much, but there's something fishy with "var". Clearly.One possible cause - just a guess as I don't know anything about "var" - would be that "var" is some global variable, that can change values but has not been qualified volatile. Then the compiler's optimizer is allowed to compile the piece of code you mentioned as you saw. But then it would mean that "var" can actually change values somewhere else - for instance inside an interrupt, or another thread, something like this. If this could be the case, then the solution would be to qualify "var" as volatile to guarantee the correct compilation of this.If this is not the case, another possible cause, even simpler, would be a missing or incorrect initialization of "var".Unless the optimizer is completely buggy, what you seem to witness could be explained by the above. If the optimizer factors out the "if", then it means it assumes "var" doesn't change values while the loops execute, which would be the case if "var" is just a local variable, or a global, non-volatile-qualified variable. Then if the optimizer doesn't prune the "else" clause, it further means it had no way, from static analysis, to know that "var" is always true.All this should give you things to look at in the code.

author: newbrain
author_rank: Super Contributor
timestamp: August 02, 2020, 06:06:57 pm
content: Exactly!Lesson #3, for reasonably decent compilers, means really: compile without optimization, then with.If the resulting behaviour differs, what part of the semantics of C am I forgetting or abusing?

author: 0db
author_rank: Frequent Contributor
timestamp: August 02, 2020, 08:24:51 pm
content: The default setup for optimization did some kind of "loop unswitching" transformation, for which you have to prove that the condition will always be constant during the loop, otherwise the optimization can't be done.The "condition" is not undefined, it's declared as =True before the loop. Thus I really don't understand, but I tried "loop unrolling" instead of "loop unswitching", and this time it worked as expected.edit:well, looking at the machine code, "loop unrolling" didn't produce any modification. Thus the generated code is the same as for the case of "no optimization".It seems the CASIO SuperHitachi C compiler has some problem with "Loop unswitching", which should only duplicate the function, one with the conditional section and one without, without altering the condition logic, but in my case it appears negated by judging what I see happening.Code:bool condition = True;...while(True){if (condition) do1();else do2(); /* deadcode */}no optimization: it worked as expected"loop unswitching": the wrong branch was executed"loop unrolling": it worked as expectedIt seems the compiler is able to determine that condition does not change and is perhaps inlining the function and optimizing out the "if branch", but for some reason it issued a negative logic when the condition is a variable rather than a constant.Code:#define condition True...while(True){if (condition) do1();else do2(); /* deadcode */}no optimization: it worked as expected"loop unswitching": it worked as expected"loop unrolling": it worked as expectedThis time it worked. Weird. Very very weird!Anyway, I do not need any "loop unswitching". It can improves the speed of the code but keeping the code size smaller is more important for me.

author: newbrain
author_rank: Super Contributor
timestamp: August 02, 2020, 08:58:54 pm
content: Ugh.A C89 compiler that can get so many things wrong should be canned without a second thought.We produce so many bugs by ourselves that we don't need any help.SuperH seems to be a target supported by gcc.I understand that in a professional/enterprise environment a compiler switch is not easy to bring about, but it can be a worthy battle to fight.When I started on my second job, we were using a C++ compiler (I've removed its name from my memory) that generated slow and mostly wrong code.It had patchy support of C++ 2.0 (this was before the C++98 standard), and was based on C++ to C translator.The day we switched to gcc (I had escaped that project since long time) we had an immediate doubling of performance and the disappearance of many unresolved bugs.

author: 0db
author_rank: Frequent Contributor
timestamp: August 02, 2020, 11:28:44 pm
content: Quote from: newbrain on August 02, 2020, 08:58:54 pmSure it is and I have just successfully compiled a gcc toolchain for SuperHThe only problem is the CASIO environment and IDE because I don't have all the source for the libraries neither the full specs nor documentation.Padding is a particularly complex part of the C specification and interacts poorly with other parts of the language, but a new defect I noticed with the CASIO C89 compiler is that you cannot absolutely compare two structs using a comparison via memcmp because sometimes the copy of a struct does retain its padding and it's awkwardly sized and aligned.It's not predictable, it's completely out of control, there is neither magic-flag nor #pragma to force it, and that's a serious problem for the software I am trying to port, and I have really enough.Thus or I will switch to Gcc, or I will rewrite the code. Or perhaps ... it is even worth having both.Lesson learned #4: an apparently "simple task" may have a lot skeletons in the closet. Never be too optimistic with anything written in C, never think that if you have two compilers and targets it will be a piece of cake switching between them.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2020, 02:00:33 am
content: Well, this C compiler sure looks like a dead cow.This reminds me of the optimizers in early C compilers and their bugs...As to structs and padding, the standard is relatively clear about that, as it's implementation-defined IIRC, and you get no guarantee about the padding's actual content (it's supposed to be unused space anyway, so you're not supposed to assume anything as to what it may contain). This makes comparing structs with memcmp() impossible, and will only work on some given implementation if you're lucky. That may suck, but I would never rely on anything regarding padding, so unfortunately, the only sure and portable way of comparing structs is comparing them member by member. Of course if you're using some pragma or attribute to define that such structs are "packed" (no padding), you can use memcmp(), but then, attributes/pragmas for "packed" structures are not necessarily available with all compilers, or may differ (although #pragma pack(...) is pretty common). Another issue with packing is that it may lead to alignment issues depending on the CPU.So, we have two different issues there. Whereas from your examples just above, it's now clear that this was an optimizer bug and nothing wrong with the code itself, comparing structs with memcmp() is OTOH clearly not portable, and should usually be avoided.For the first, switching to GCC is guaranteed to get you a correct result with any level of optimization, but for the struct comparison thing, I would suggest rewriting your comparisons.

author: 0db
author_rank: Frequent Contributor
timestamp: August 03, 2020, 08:09:31 am
content: Quote from: SiliconWizard on August 03, 2020, 02:00:33 amThat's how I am rewriting the original source code (not written by me), but it's a lot of work and to be honest I felt a bit lazy at the beginning and I looked for an easier "voodoo-workarounding" with just the C compiler

author: newbrain
author_rank: Super Contributor
timestamp: August 03, 2020, 08:49:03 am
content: Comparing structs with memcmp if of course not guaranteed to work, even in the absence of padding, depending on the target architecture and C implementation.Classic examples are (real mode) x86 pointers with segment:offset, the same pointer can be represented by different values.Other examples include +/-0, floating point NaN etc.Even using equality operator (==) might have surprising results if some of the fields are floating point, and NaN or Inf are involved - these should be specially treated according to the desired outcome, using the macros in <math.h>.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2020, 04:35:20 pm
content: Quote from: newbrain on August 03, 2020, 08:49:03 amYes, of course that was assuming that the binary comparison of all members made sense, which is not always the case.FP numbers are a good example. Exact equality of FP numbers usually doesn't make sense anyway, so yes, '==' is to be avoided as well in most cases. You should usually compare the absolute value of the difference with some small "epsilon" instead.Related bonus question: before dividing some FP numbers (N/D), does comparing the divisor D like so guarantee that it will never raise a divide-by-zero condition?Code:if (D != 0.0)Q = N / D;As to pointers, I guess that's another one too. I didn't find anything in the standard that guarantees equality of pointers pointing to the same memory location, except for statically allocated objects, for which it says that pointers to a given statically allocated object should always compare equal. Although I'm not sure I found this crystal-clear in the std, I'm assuming this is true when you use the "&" operator, or for arrays. Once you mess with pointer arithmetic, I guess all odds are off strictly speaking, although it's relatively common to see pointer comparisons as some sort of "sentinel value" when going through some memory block. Whereas it usually works, I guess this may be slippery on some platforms using segmented pointers?The usual approach to comparing structs is to write comparison functions for each struct type for which you need a comparison.It's a general rule of course, but DO NOT duplicate code for doing this. Write functions.

author: Renate
author_rank: Super Contributor
timestamp: August 04, 2020, 01:11:38 pm
content: I don't see garbage in the padding to be a problem.If the struct is on the stack or allocated I always do a memset(0);If it's initialized then the compiler put in zeroes in the static copy that it memcpy's.Code:struct {char a; int b;} q={0xfe, 0x12345678};printf("%08x %08x", ((int *) &q), ((int *) &q));I can't think of many (any?) times that I compare a whole struct, usually it's just the identifying field for a match.If I needed to do that then memcmp would be good enough for me (given that I know the padding is zero and who uses selectors anyway?).As far as pointers go, if you're getting that far, maybe you need a deep compare to see if two structs are equivalent.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 04, 2020, 06:45:45 pm
content: Quote from: SiliconWizard on August 03, 2020, 04:35:20 pmIt does; but not from the overflow or underflow conditions, nor does it ensure the result is finite.In C, a floating-point division can be a divide-by-zero, overflow (result inf or -inf), or underflow (result 0 or -0).Also, when the result is not exact (say, 3/7, which is an irrational number without exact representation in IEEE-754 Binary32 or Binary64, which current architectures tend to use for floating-point math), the inexact condition will occur, but it is irrelevant to most people.