All variables should be Global - Page 1
Forum section: electronics / general

author: @rt
author_rank: Super Contributor
timestamp: February 27, 2017, 02:45:35 pm
content: Hi Guys,That, I believe, is what’s known in the biz as clickbaitIt occurs to me that in microcontroller land, and in C, that all functions should be void functions,and have no arguments passed to them, or returned from them by anything other than global variables accessed directly within the function.Why is anything other than the above, anything other than a waste of time using a stack for nothing?.. other than some guy with a suit & tie said: “Variables should be private unless global in their nature”.I know the obvious reason of the Human programmer making a mess, but is there a real reason?

author: capt bullshot
author_rank: Super Contributor
timestamp: February 27, 2017, 02:49:36 pm
content: There are many reasons to do so:limited ressources (RAM), limited ressources (Flash memory), and limited ressources (processing speed)Coding OOP style on a 8K Flash / 256B RAM micro is considered bloat ;-)

author: @rt
author_rank: Super Contributor
timestamp: February 27, 2017, 03:04:33 pm
content: Yes sure your variables can be reused, but if you can get away with it...you could also do faster, and sometimes save memory.I can’t see any way to get past the copy to some other memory or stack where the arguments are passed,unless that’s where an optimiser comes in.Code:x = 0; y = 0; // passed as argumentswhile (x < 100) {setpixel(x,y); x++;}x = 0; y = 0; // globalwhile (x < 100) {setpixel(); x++;}

author: cyr
author_rank: Frequent Contributor
timestamp: February 27, 2017, 03:19:26 pm
content: Using global variables may stop the compiler from doing optimizations like sticking the values in registers...Having code that is readable, maintainable and reusable are "real" reasons BTW.

author: donotdespisethesnake
author_rank: Super Contributor
timestamp: February 27, 2017, 03:25:25 pm
content: Quote from: @rt on February 27, 2017, 02:45:35 pmThat is the real reason. Programmers are human.BTW, you failed the interview, sorry

author: @rt
author_rank: Super Contributor
timestamp: February 27, 2017, 03:40:52 pm
content: QuoteYou already know that I already know not to show that to an interviewer, but yeah, I know

author: hans
author_rank: Super Contributor
timestamp: February 27, 2017, 03:47:44 pm
content: I think these styles can be architecture specific. For example, on 8-bit PIC global variables is actually employed in the background, because that's basically the only efficient memory model it supports to run C.But it is done for you, and the compiler can make choices on reusing certain bits of memory depending on the call nesting of your program and which local variables exist at what time. I would argue to not try to beat the compiler.Quote from: @rt on February 27, 2017, 03:04:33 pmI think you'll find neither is significantly quicker or memory efficient on something like ARM. The first has to push/pop variables onto the stack (which it does anyway as per the calling convention), and the other needs to store variables to fixed addresses (which is cumbersome on ARM) and load them too. I suppose both will have very similar levels of overhead.But in all cases I would favour the first one. It is more readable, more maintainable, less of a mess , and most importantly probably a lot better inlineable by the compiler and thus quicker.And useful for some: unit testing is a lot easier when you don't have tons of global variables that need housekeeping.

author: cyberfish
author_rank: Regular Contributor
timestamp: February 27, 2017, 04:34:19 pm
content: Quote from: @rt on February 27, 2017, 02:45:35 pmIf you set arguments as globals and return values as globals, you are just doing the compiler's job for it, and it can probably do a better job than you at passing and returning values to/from function.When you have a modern compiler, on many architectures, arguments and return values aren't actually passed using the stack. Registers are used for the first few arguments instead. Return values are often not copied at all (return-value optimization). Even if the stack has to be used, many architectures have dedicated instructions for using the stack, that will be faster than doing indirect load/stores to globals. On ARM for example, there are instructions to push and pop multiple registers in a single instruction. On higher end microcontrollers with cache (like some Cortex-M7-based ones), stack also has very good temporal AND spatial locality.Function call mechanism has been optimized to death already by both hardware designers and compiler writers. It's unlikely that you can beat it.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: February 27, 2017, 05:21:15 pm
content: One word: interrupts.Reentrant and recursive programming requires stack (or if not hardware stack, then a software emulation of it).That's still no hard requirement -- interrupts can usually be serviced quickly, and usually one at a time. But it's a lot easier to deal with things if they can pile up a modest amount.You can design a system so that interrupts are used more like events, where multiple concurrent events (even from the same source) can be executing. An example might be a global timer interrupt, which fires off housekeeping activities periodically (reading inputs, swapping tasks in an RToS, etc.). Each of those events is stored on the stack, on top of the interrupt that started it. That interrupt might fire again before the first one has finished, in which case, a non-reentrant version gets its variables trashed!Not to say functions should be exclusively reentrant, either: it can be very useful to keep a global state, to say for example: if an optional function is still waiting on the stack, skip over it this time. This might be implemented by setting a static (global) "busy" flag within the function (and clearing it at the end of the function), and checking that flag before jumping into the function again.A universally applicable case of this: checking stack size/position/usage. If you have an arbitrarily recursive program, with no provable limit on stack requirements, and you outright ignore how much stack is being used, then some day, your program is going to steamroll through other memory. Madness ensues.Which relates to the theoretical concept: a Turing-complete machine has unlimited memory, and can recurse  infinitely. An ideal Turing machine does not need to check for stack size, because it won't run out. All our so-called "Turing-complete" machines are semantically complete, but finite in storage, so they cannot truly compute any computable function. (Example: no one has calculated what Ackermann(999, 999) is, but it's proven computable. Oh, also... we only have finite time to compute something. So, say, while it's absolutely possible to find collisions in any hash function, a SHA-1 collision has been realized only recently.)So, as programmers of not-quite-Turing-complete machines, we must be aware of their limitations, and dirty our theoretically ideal functions with necessary precautions.Tim

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: February 27, 2017, 05:25:15 pm
content: http://koopman.us/bess/chap19_globals.pdf

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: February 27, 2017, 05:32:58 pm
content: Unless you're doing high volumes, dev &debug time costs more than silicon, so using a slightly bigger part to make coding quicker or more reliable is a no-brainer.By making things unnecessarily global you are depriving the compiler of info that could be used for optimisation.One thing I feel is missing from C is the concept of local procedures, like Pascal, so you have a mid-way point between local and global vars.

author: rstofer
author_rank: Super Contributor
timestamp: February 27, 2017, 05:35:43 pm
content: Sounds a lot like Fortran COMMON to me!For very large programs, including those with overlays, COMMON blocks were used to statically allocate variables. Whether or not that is a good idea is something I'll leave to the computer scientists.The first computer I ever programmed was an IBM 1130 with 8k words of memory. It wasn't unusual to have user subroutines loaded on call. Even system routines could be loaded on call, the Fortran compiler had 27 overlays (called phases). This resulted in a lot of disk activity but memory was expensive. Mainline programs often had a massive COMMON statement that carried through to all the subroutines. The only good news is that it was possible to duplicate the cards and simply add them to the deck.Data hiding and protection seems like a much better approach. At least keep variables file static. The whole world doesn't need to know.

author: tszaboo
author_rank: Super Contributor
timestamp: February 27, 2017, 05:42:29 pm
content: Quote from: T3sl4co1l on February 27, 2017, 05:21:15 pmThe keyword "volatile" should be used by all data, which is acessed by interrupts, and then magically it will work.To OP: Avoid "new" and "malloc" (or use malloc once and never free it) define everything compile time, dont use pointer list and silly things like that. Otherwise you can return an int for a function. What you must avoid is memory fragmentation, and running out of memory. Dont be so strict, it is unnecessary, unless some automotive silliness requires it. So yes. Use local variables, it is fine. Just give the stack some space.

author: brucehoult
author_rank: Super Contributor
timestamp: February 27, 2017, 06:04:02 pm
content: Quote from: @rt on February 27, 2017, 02:45:35 pmWell, that's just wrong.In AVR land wth a C compiler, the first 18 bytes of function arguments are in registers. The first 8 bytes of those you just go ahead and use, the next 10 you have to make sure you save and restore before you yourself return.8 bytes is usually plenty, if your arguments are pointers or 8/16 bit integers. Why on earth would you want to use extra code and time to copy those to global variables, and then time and code to read them from the global variables in the called function? Decent modern compilers can usually have those values generated into the right place for a subsequent function call, so you don't even have to use code and time to move them there.On ARM, the first 16 bytes (up to four arguments if 32 bits or smaller) are passed in registers. Again, usually enough.On RISC-V (which I have in my "HiFive1" Arduino clone, and may well be found in increasing numbers in embedded work in future) the first 32 bytes of arguments (up to 8 arguments) are passed in registers. And the called function had all those eight registers to play with, plus another seven "temporary" registers, before it even has to think about touching memory.As for global variables vs local variables in the main function ... it makes no difference. Most systems are allocating global variables starting from some low address (just above the code, if code isn't in another address space). First the initialized variables (copied from flash or wherever) and then the zero-inited variables. Then you have the "program break" and space after that is allocated by sbrk() (or malloc() calling sbrk()). The stack starts from the top of memory and works its way down towards the program break. If they ever meet .. bad news. It makes basically zero difference whether something is a global, allocated by malloc() at program startup (and never released), or or in the stack frame of main(). You've got the same memory use either way, with the only difference being exactly where the stack and program break will meet (if they do).Systems don't have to be organized that way, but these days most that use C have converged on the same layout.For functions other than main() ... if several functions share some data, and there's only every one copy of it .. sure, make it a global rather than passing around a pointer. Things such as counting how many times something happened in a function, spread over many function calls, should be globals.Temporary data, used only by one function (or by functions it calls) should be on the stack. The stack gives you automatic overlays of the memory areas used by different functions that are called at different times (and don't call each other). The *only* valid reason not to use data on the stack is if your CPU has poor support for a stack. That's the case for some older CPUs such as 6502 (and PIC?), but it's most definitely not true for AVR or ARM or even anything derived from 8080/Z80.

author: hans
author_rank: Super Contributor
timestamp: February 27, 2017, 09:20:18 pm
content: The 8-bit PICs only have a HW stack for program counter only if I recall. All data must be passed through RAM, with perhaps the exception of 1 argument and return which can be passed through the processor register.That's why I said in my previous comment that this style of coding could be architecture dependent, if indeed a programmer tries to target such a platform. But a modern compiler like XC8 will have stack emulation techniques built in (take a moment to browse in the compiler settings). I recall the default one tries to achieve the most compact variable layout with most amount of sharing depending on the call tree, but there were 2 others one as well.On PICs it's not uncommon to have functions compiled twice, in particular when it needs to be reentrant from ISR and main code. Some algorithms may also have some kind of recursiveness (maybe only 1-2 calls deep) which will make trouble using globals. Having the compiler handle this for you, instead of manually coding this using globals, is a big relief.Trying to beat that level of compiler ingenuity is not worth it IMO, but I'm not surprised it's being done. It depends on what you need I suppose, maybe automotive that demands simple CPUs use these chips and also wants to eliminate any "intelligence" not in control of a human between build cycles. But then I would say: write assembler. If C is truly a portable assembler, I wouldn't want to worrythatmuch about the memory model of my target architecture.I think it's also common censuses on this forum that we rather code things well than code things for mass production (qty: >100k-1M) devices.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: February 28, 2017, 04:25:07 pm
content: Execution efficiency should matter. Trust me it will one day. Remember extra cycles used in the name of clean code use extra energy.How much energy is wasted by CPU's having to clock faster to process suboptimal code.It's my pet peeve because i write code for micros running on batteries so I like to be stingy with the cpu cycles for battery life.Would somebody care to explain how globals can be less efficient in terms of execution time.Oh and please let me have it regarding my uninformed and silly ideas.

author: cyberfish
author_rank: Regular Contributor
timestamp: February 28, 2017, 04:27:33 pm
content: Quote from: snarkysparky on February 28, 2017, 04:25:07 pmA few people (including myself) have already. Read replies above.Using function arguments is faster, uses less RAM, and also cleaner. Also more flexible (allows recursion, and calling from interrupt handlers). Main reason is that most arguments are passed using registers anyways, which is faster than globals.

author: Jeroen3
author_rank: Super Contributor
timestamp: February 28, 2017, 04:45:24 pm
content: Functions are also a complete waste of time. You should put everything in assembly!Now serious. C and C++ are all about keeping data where is belongs.If you put everything global you will have a lot of data where it does not belong. This will impede other coders, and eventually also you, in understanding what is going on.C has a few scopes where data is relevant.1. Global, referenced from everywhere. No protection from mistakes whatsoever.2. File, referenced only from file.c, use the static keyword to enforce this.3. Static function scope, this variable has the static keyword used on it in a function. It can only be referenced from function scope. Between the {}. However, it is allocated permantently. Meaning it's like a global, but private.4. Function scope, these variables are only allocated for function duration. Such as the arguments. You'd be making a mess with "tempvar" if you insist in making these global.5. Block scope. Wherever in the code you can nest a block {}. You can do this for the editor the collapse the block. But in strict ansi c you can use it to create another level of temporary veriables only allocated during the block. (Since you can only do this at the start a of block) Useful hints for you and the compiler. Anforwith {} has such block. There are often iterators inside.Ignoring the implementation of ancient or crap compilers, it would be a waste not using the tools on hand in C to create more structured and segmented code.You can put everything in 1 file, or even in main. Or all assembly. But it wastes the most expensive commodity on hand.developer time.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: February 28, 2017, 04:51:25 pm
content: Well what I read there concerns using the registers which are faster. But doesn't this need to assume that the values are already in the registers when the function is called. That certainly would be the case many times but not always. Some CPU operations can act directly on memory without loading to a register so this would seem to be a plus for globals. But I guess the main idea is that variables passed through the function interface are not necessarily copied to the stack if they are small enough for registers. I get it.

author: brucehoult
author_rank: Super Contributor
timestamp: February 28, 2017, 05:05:21 pm
content: Quote from: snarkysparky on February 28, 2017, 04:51:25 pmNo CPU acts on memory without copying it to a register. It's just that the temporary register used doesn't have a name and you can't see it.Accessing memory takes probably ten times the energy of accessing a register that already has the value.

author: cyberfish
author_rank: Regular Contributor
timestamp: February 28, 2017, 05:09:11 pm
content: Quote from: snarkysparky on February 28, 2017, 04:51:25 pmIf the values are in memory and not in registers, using globals would require copying those values from memory to memory first. m-2-m is slower than m-2-r on most architectures, and maybe equal on some.The only time it can be faster is if the argument is already in the global you need. This is almost never going to be the case, unless you have a chain of functions you know you'll be calling one by one. In this case using arguments and return values still beat it because it allows the optimizer to do return-value optimization which eliminates all those copies.Using globals also stop the compiler from doing optimizations like function inlining, which even eliminates the function call overhead, and allows more optimizations to be done following the inlining.Modern optimizers are very smart. A lot of times the best thing you can do is to write your code in the simplest and cleanest way, to make the optimizer's job easier. Unless you know exactly what you are doing, and you know the architecture better than the compiler, being clever will often just confuse the optimizer and you get sub-optimal code.Don't make your code ugly for no reason. In this case it's even slower.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 28, 2017, 05:30:59 pm
content: Quote from: mikeselectricstuff on February 27, 2017, 05:32:58 pmNested functions are available in GCC as a extension.AFAIK they are not yet in ANSI standard C.As to the original question - why do (some) embedded programmers seem to feel that they can abandon all good coding practices as suits their whim?If everything is a global your code will not be re-entrant, and your compiler might miss optimisations but more importantly anything which is longer than a single A4 page will simply become unreadable and unmaintainable. You will also invariably create bugs by using the wrong variable accidentally. Don't do it.

author: cyberfish
author_rank: Regular Contributor
timestamp: February 28, 2017, 05:35:21 pm
content: Quote from: grumpydoc on February 28, 2017, 05:30:59 pmIt has been available in C++ since C++11 though!Code:int main() {auto increment = [](int x) -> int {return x + 1;}int a = increment(2);}

author: slicendice
author_rank: Frequent Contributor
timestamp: February 28, 2017, 05:43:56 pm
content: Quote from: Jeroen3 on February 28, 2017, 04:45:24 pmLOL, writing Assembly the correct way (the most memory conservant and efficient) would be the equivalent of a C-function with input parameters and a return value (if you need them, depends of application complexity), and local variables for just enough values to get the function job done. The simpler the function, the better. You want to stick to the registers only, as far as possible, and if the code must jump, it should do a short jump (calling the stack) and try to avoid long jumps (getting new instructions from memory) as much as possible.A variable is just a memory address, the fewer addresses you need at one point in time, the better. Defining everything global would require huge amounts of memory, or you would have a hard time making sure variables are not overwritten.Using ANY modern C compiler today, should produce really good Assembly code for the most basic functions. There should not be any big difference between writing Assembly or C. I am not sure how the ARM compilers behave, as I have not tested it, but all C compilers has produced exactly the same Assembly code as I have written manually in Assembly. There are however a lot of exceptions to be aware of, and this is where writing pure Assembly comes in play. The difference in size can be many many bytes for just one single instruction. Sometimes there are multiple solutions(in Assembly) for the same simple problem, but the size of your code can differ a lot between them.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 28, 2017, 05:44:44 pm
content: Quote from: cyberfish on February 28, 2017, 05:35:21 pmAs if C++ was not a deep enough quagmire of features ready to drown yourself in!

author: cyberfish
author_rank: Regular Contributor
timestamp: February 28, 2017, 05:47:52 pm
content: Quote from: grumpydoc on February 28, 2017, 05:44:44 pmIt's a difficult language to learn, but once you are proficient with it it's amazing!You get all the performance benefits of C (sometimes even faster than C from things like template meta-programming), with high level constructs that save a lot of time/code.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: February 28, 2017, 06:10:06 pm
content: Quote from: cyberfish on February 28, 2017, 05:35:21 pmWith incredibly painful syntax that only a demented language designer could love.

author: Cerebus
author_rank: Super Contributor
timestamp: February 28, 2017, 06:38:24 pm
content: Quote from: Sal Ammoniac on February 28, 2017, 06:10:06 pmI don't think any actualdesignwas involved.If you think that syntax is painful to use or remember, try writing a parser for it - the actual grammar is about as irregular and inconsistent as you can get without actually deliberately setting out to make it that way.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 28, 2017, 06:56:30 pm
content: Quote from: cyberfish on February 28, 2017, 05:47:52 pmAs a professional software engineer I have written a lot of C++ although I am a bit out of touch with some of the newer language features including lambda expressions. EDIT: I left full-time IT in 2003. I just tinker these days.As a softwaredeveloperI agree that it is a nice language to work with, it is expressive and nuanced and you can write elegant code if you wish.With a more managerial hat on, however, I grew more and more disillusioned with the language - the more large projects are tempted to use the "neat" features of the language (especially templates and now, I suppose, lambdas) the more they become difficult to maintain and the bar to new staff joining a project becomes ever higher. Large, complex C++ programs can be impossible to comprehend because they simply defeat static analysis.I think part of the problem is that the C++ committee likes to add "powerful building blocks" to the language but forget that these features need a very responsible hand when in use. The nested functions thing is a case in point - rather than add nested functions they chose to add lambda bindings sufficiently powerful to allow them to be used to implement nested functions. Templates is another example - did you realise that the template language is actually Turing complete and there are examples of code which causes the compiler to emit (eg) Fibonacci sequences or lists of prime numbers as a side effect ofcompilation. I mean, did we really need thatThese days I actually prefer well crafted C

author: cyberfish
author_rank: Regular Contributor
timestamp: February 28, 2017, 07:08:26 pm
content: Quote from: Sal Ammoniac on February 28, 2017, 06:10:06 pmYeah. That's because lambda expressions are much more useful than just for nested functions.It also allows you to pass those functions around as objects, and bind local values and references to them. That makes async code much cleaner.When used as nested functions, the syntax does seem over-engineered.QuoteThis I totally agree with. It's one of the main differences in design philosophy between Java and C++. Java = don't include features that can be abused. C++ = include features as long as there are uses for them.This is why we don't have operator overloading in Java for example (very useful if used correctly, but easy to abuse), and we still have goto in C++ (can be used to break out of nested loops, which would otherwise require a bunch of flags, or abusing exceptions). I would much rather work in C++ than Java, but I would much rather work with an incompetent Java developer than an incompetent C++ one.Luckily everyone I work with are highly competent, so I don't worry about that too much.

author: tggzzz
author_rank: Super Contributor
timestamp: February 28, 2017, 07:38:40 pm
content: Quote from: grumpydoc on February 28, 2017, 06:56:30 pmAll very very true, unfortunatelyIn the early 90s I decided that "if C++ is the answer, then you ought to change the question". Unfortunately nothing has caused me to change that somewhat aesthetic decision. Back then the two key points for me werethe template language "features" you mention, because they werediscoveredand thedesigncommittee didnot believe ituntil someone rubbed their noses in it. I prefer my tools to be designed, not discoveredthe endless discussions as to whether it "casting away constness" should be mandatory or should be forbiddenMuch later, the amusing/frightening C++ FQA emerged. Anybody contemplating using C++ should understand its ramifications inside out before using the language.

author: hans
author_rank: Super Contributor
timestamp: February 28, 2017, 08:13:34 pm
content: I thought it would be nice to benchmark said claims on 2 architectures: PIC24 and PIC32.I chose these because they have simulators in MPLAB X. And both chips have GCC-based compilers.Also interesting is the contrast between both architectures. The PIC24 has it's CPU registers mapped onto the address bus, and some instructions can access RAM (even like INC , ). However most ALU instructions need to go through the register file.The PIC32, like ARM CPUs, needs more explicit instructions to access RAM. Mainly via pointers only.Now, let's run this code:Code:#include <stdint.h>#include <xc.h>uint16_t gx = 0;uint16_t gy = 0;uint16_t result = 0;uint32_t s = 0;inline void doStuffWithGlobals(void){result = (gx%9) * (gy%3);}inline uint32_t doStuffWithParameters(uint16_t x, uint16_t y){return (x%9) * (y%3);}uint32_t benchmarkParams(void){uint16_t x,y;uint32_t r = 0;for (y = 0; y < 0xFF; y++){for (x = 0; x < 0xFF; x++){r += doStuffWithParameters(x,y);}}return r;}void benchmarkGlobals(void){for (gy = 0; gy < 0xFF; gy++){for (gx = 0; gx < 0xFF; gx++){doStuffWithGlobals();s += result;}}}void stopwatchStart(void){T2CON = 0;T2CONbits.T32 = 1;TMR2 = 0;TMR3 = 0;T2CONbits.TON = 1;}uint32_t stopwatchStop(void){T2CONbits.TON = 0;return ((uint32_t)TMR2) | ((uint32_t)TMR3 << 16);}int main(void){volatile uint32_t timeParams = 0;volatile uint32_t timeGlobals = 0;stopwatchStart();volatile uint32_t s1 = benchmarkParams();timeParams = stopwatchStop();stopwatchStart();benchmarkGlobals();volatile uint32_t s2 = s;timeGlobals = stopwatchStop();// s1 should be s2while(1);}In all cases the computed result is correct. The algorithm I try to benchmark is just some bogus thing, but at least the compiler isn't clever enough to optimize away the complete algorithm straight away.For PIC24 I used XC16 1.30 GCC -O1 + "do not override inline".With inline: Time of parameters: 1,762,339 ticks Time of globals: 1,761,832 ticksWithout inline: Parameters: 3,772,759 Globals: 4,097,871If I change the algorithm to simply x*y I get (with inline):Parameters: 456,727 Globals: 456,476Without inline: Parameters: 1,889,837 Globals: 1,889,578Actually quite surprising results. The code actually marginally faster. Now that's cool I suppose, but it's not faster in all cases. Especially on non-optimized code like without inline.The reason I did not want to focus on x*y though, is because the compiler sees that sequence of operations and optimizes multiplication out of the code. It's not far off from assigning a fixed value to the result.On PIC32 the results are drastically different:(XC32 1.30 GCC -O1 -finline-functions)Inline: Parameters: 848,916 Globals: 978,200No inline: Parameters: 848,916 Globals: 2,342,448Multiplication (inline): Parameters: 327,171 Globals: 391,447Implicit vs explicit compiler optimizations become very clear at this level (the compiler tried to inline the parameterized function anyway). It also shows that RAM access with huge address spaces is a lot more expensive.That's why they have register files and caches to speed programs up.

author: moz
author_rank: Regular Contributor
timestamp: February 28, 2017, 09:37:30 pm
content: The rule of thumb I use is that good code is more than 90% maintenance. Only bad code is less... it means no-one is using it, or no-one is willing to risk modifying it.Quote from: tggzzz on February 28, 2017, 07:38:40 pmThis. I started a project with some specific feature requirements that at the time could really only be met by C++ (talking to embedded hardware via internet-exposed encrypted IP). Sadly Scala, Rust and Go were all 90% there. C++ is awesome, whenever there's a question of features they settle for "all of them". You can do just about anything with the language. Which makes it a bit too much like Perl for my liking - it's very easy to produce write-only code. And the lack of a first class unit test framework makes me cringe.I have ended up writing "C++ lite" most of the time. No Boost, very few templates, but I use selected features like lambdas a lot because coming down from a modern language it's very hard not to think in lambdas.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: February 28, 2017, 09:46:45 pm
content: Quote from: hans on February 28, 2017, 08:13:34 pmAny speculation as to why globals took so much more time without inlining?

author: tggzzz
author_rank: Super Contributor
timestamp: February 28, 2017, 10:05:33 pm
content: Quote from: moz on February 28, 2017, 09:37:30 pmThat's all valid.A problem can be that each project and/or developer and/library implicitly chooses a different subset, and they don't play well with each other.Of course some subsets of some languages have significant tooling and momentum, e.g. MISRA C and SPARK ADA.

author: TNorthover
author_rank: Contributor
timestamp: February 28, 2017, 10:12:14 pm
content: Quote from: snarkysparky on February 28, 2017, 09:46:45 pmThe test looks simple enough that (with MIPS's 32ish registers) virtually nothing would have to be stored to perform the calculation. Parameters automatically live in registers with or without inlining, so there's no barrier there.But for globals you need to calculate their address and be store/load them at each function boundary. With inlining, the compiler can remove all the paired load/store operations because it sees them in the same function. Without, no such luck.

author: hans
author_rank: Super Contributor
timestamp: February 28, 2017, 10:55:20 pm
content: I took a look at the disassembly. GCC dares to inline the multiplication routine even at level O1 and no explicit inline statements. It only does that with globals at level O2. At O2, you get the same results as with explicit inline. Eventually the compiler does get to a similar level of performance.However, by nature of a global, the compiler is more careful with optimizing at lower aggressive levels. Perhaps there also situations where a piece of code written using globals doesn't get the 'special treatment'.This is indeed a really simple algorithm. Not a lot of data is being passed around. A lot of time is crunched in the inner loop. I suppose that the globals wins out because y only changes once every 256 inner loop ticks. For a global call it doesn't have to move the y value explicitly. Thiscouldsave some time, but in respect to inline it's almost neglible. But that's only 1 case, maybe some others are different. But it certainly is not a general notion to take note of.To be honest I'm surprised that the compiler isn't able to optimize my little bogus routine to an integer value.These days in C++ you can generate complete random mazes at compile runtime, perform pathfinding algorithms on them, format the grid, and display it. And it all compiles back to down a const string.https://youtu.be/3SXML1-Ty5U

author: moz
author_rank: Regular Contributor
timestamp: February 28, 2017, 11:19:57 pm
content: Quote from: tggzzz on February 28, 2017, 10:05:33 pmThat's another reason I chose C++, my coworkers at least use C on the embedded side so my code is fairly readable for them. It also means that the data structures look quite similar on both ends of the comms channel ("again with the debugging. Always with you the debugging" in my best Jewish mother-in-law voice).

author: moz
author_rank: Regular Contributor
timestamp: February 28, 2017, 11:33:22 pm
content: Quote from: hans on February 28, 2017, 10:55:20 pmDo you have access to a library common across architectures? I'm thinking of something like crypto or FFT. That way you can pick one function, hack a version to use mostly globals, and compare.I have a wee crypto library (TinyAES) that I use for testing because it's all in C rather than the hand-optimised OpenSSL code that is much faster but makes debugging tools really unhappy (valgrind says it uses un-initialised values, OpenSSL say it only looks that way. Etc). But it's complex enough that I can compare real-ish performance just by saying "AES128 this" and get an idea of CPU performance in 10 lines of (my) code.It also depends on which C complier you're using, and which C standard. C99 at least lets you have local static variables, which are better than globals for reasoning about the code (whether it's you or the compiler doing the reasoning). C11 adds thread-local variables and static asserts, again making life easier. But also making the "everything global" claim even less credible.

author: cyberfish
author_rank: Regular Contributor
timestamp: March 01, 2017, 12:41:11 am
content: Quote from: tggzzz on February 28, 2017, 07:38:40 pm"If C++ is the answer, the problem is too complicated for someone who only writes C."I hope you are aware that the first C++ standard was published in 1998 (so what you looked at wasn't even the first revision), and there has been 3 more standards since, and C++ today is nothing like C++ back then? It's very different even from C++03 (the latest standard before 2011).For example, (almost) no one does manual memory management anymore. I work as a professional C++ developer and I haven't written any "new" or "delete" in many months. There's also automatic type inference now, saving people a lot of typing.I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.Templates have their problems, but we don't know any better way to achieve the same, and it's so useful that even Java copied it (and then bastardized it, but that's another discussion).

author: tggzzz
author_rank: Super Contributor
timestamp: March 01, 2017, 01:05:59 am
content: Quote from: cyberfish on March 01, 2017, 12:41:11 amI am aware of that.I started using C in 1982, when there were precisely two books on it.I started using C++ in 1988, and having been liberated by grokking Smalltalk, I preferred Objective-C.In the early 90s I kept using C, kept a watching eye on C++'s glacial progress, and was unpleasantly surprised by the standardisation process. As I said, I prefer my tools to be designed, not discovered. ("Oh look, my multimeter also solders wires")I also like my tools to solve my problem, not be an added problem. "Language lawyers" are far too much part of the C++ ecosystem; they shouldn't be necessary.QuoteNot to anything like the extent possible with a strongly typed modern language.Remind me, is it possible to "cast away constness" now?QuoteI don't see how that is relevant to anything I wrote.QuoteC++ wasn't the first to employ templates. (Old observation: academic papers about C++ refer to other academic papers about C++, whereas those for language X refer to languages X, Y, Z. That's a reflection of the C++ community being too self-absorbed and inward-looking for its own good)Java templates are a pigs ear due to the requirement for "type erasure" to ensure backward compatibility. Even so, compilers produce comprehensible error messages, and debuggers produce understandable, correct output.I suspect C# templates are better, but I have no interest in that language/environment.

author: cyberfish
author_rank: Regular Contributor
timestamp: March 01, 2017, 01:26:50 am
content: Quote from: tggzzz on March 01, 2017, 01:05:59 amYes, and it has been the case since the very first standard, so I'm not sure why you added the word "now".QuoteQuoteUnless you literally meant someone wrote a C++ compiler, didn't write code to support templates, and it turned out to magically support templates? (this would be equivalent to your multimeter and soldering example)

author: westfw
author_rank: Super Contributor
timestamp: March 01, 2017, 01:54:47 am
content: The idea that variables on a stack are less efficient to access if they're on a stack than if they're global is just ... wrong.Consider "i += 1; // increment variable."on AVR:Code:;; globals:lds r16, i ; load from global: 4 bytes, 2 cyclessubi r16, -1 ; increment: 2 bytes, 1 cyclests r16, i ; store to global: 4 bytes, 2 cycles;; localsldd r16, Y+i ; load from stack: 2 bytes, 2 cyclessubi r16, -1std r16, Y+i ;store to stack: 2 bytes, 2 cycles.67% bigger to access i as a global. Z80, 68xx, 6502 all have some form of indexed addressing and should end up using similar code.It's 100% bigger on an ARM, because there's no LDS equivalent:Code:;; Globalldr r3, [pc, #8] ; get address of i (6 bytes!)ldrb r2, [r3, #0] ; get iadds r2, #6 ; addstrb r2, [r3, #0] ; store;; localldr r3, [sp, #4]adds r3, #6str r3, [sp, #4]PIC16f might be able to do the global in 2 instructions, but the compiler I tried implemented the "local variable" version as a global anyway. PIC18 and "enhanced PIC16f15xx" implement some form of indexed addressing, so they get better at handling a stack, even though there is no "hardware stack" (we remember that stacks are just index registers with some autoincrement/decrement modes, and "stack frames" don't actually need those...) MSP430 might be able to add immediate values direct to memory, but the instructions start to get long and slow...And that's without worrying that you need a separate "i" for each subroutine, if you're using globals.Argument-wise, if your arguments aren't passed in registers (which is common with modern processor) or on the stack, you eventually end up with the (old) BASIC-like syntax where you end up moving your arguments into the correct globals:Code:sub1_param1=thisvalsub1_param2=anothervalsub1();(assuming you re-use your subroutines more than once, which ... is part of the idea, you know.) This is obviously no better than moving the values to the stack (and might be worse, as per the above instructions.)So, in the absence of an actual example where using globals is more efficient, I claim your entire premise is incorrect!

author: snarkysparky
author_rank: Frequent Contributor
timestamp: March 01, 2017, 02:24:33 am
content: I have read on this issue that if there a many variables needed in a routine then define a struct containing the variables and pass a pointer to it to the function. Is this a good way to handle many variables.As an example suppose there is a "motor" datatype created with parameters such as current, speed, voltage, acceleration, temperature...etc.functions get the pointer to the struct so they can read and modify the values.How about if the struct variable is declared with global scope??

author: cyberfish
author_rank: Regular Contributor
timestamp: March 01, 2017, 02:51:31 am
content: Quote from: snarkysparky on March 01, 2017, 02:24:33 amThere's no performance difference in this case. Structs (of sizes larger than the largest int type usually) are always passed by pointers anyways (copies are made by caller if necessary).This is also the sort of basic optimization that any half decent compiler will be able to do, so it's best to just keep the code as clean and intuitive as possible.

author: TNorthover
author_rank: Contributor
timestamp: March 01, 2017, 03:08:52 am
content: Quote from: cyberfish on March 01, 2017, 02:51:31 amThis is roughly true for parameters passed by value (it's very ABI-dependant). But if the routine needs to modify a lot of variables (and so passes disparate values by pointer/reference) I could see putting them together in a struct would be beneficial. You'd possibly save a double-indirection, and the data would be more cache-local if that mattered to you.Regardless, I'd always recommend making these decisions based on maintainability rather than pure efficiency. At least until there's no other option.QuoteTotally agree about the second part, but compilers aren't very free to meddle with parameter passing because functions can usually be called from other files. You need link-time optimization to have a hope in hell, and even then this kind of optimization is most charitably described as an emerging field.

author: cyberfish
author_rank: Regular Contributor
timestamp: March 01, 2017, 03:37:59 am
content: Quote from: TNorthover on March 01, 2017, 03:08:52 amThat is true. I'd hope everyone is using LTO by now!I don't usually pay too much attention to this, because if a function is small enough to be inlined, calling convention doesn't matter anymore. If it isn't, it's probably big enough that call overhead is negligible anyways. Though I guess this is not true if you are highly flash-constrained, and have to optimize for size instead of speed.

author: brucehoult
author_rank: Super Contributor
timestamp: March 01, 2017, 03:55:39 am
content: Quote from: TNorthover on February 28, 2017, 10:12:14 pmHi Tim, what brings you here from llvm land?I was a bit confused when you started talking about MIPS when the OP was talking about PIC32. I looked at 8 bit PIC once and ran screaming back to AVR. Didn't realize they'd picked up a decent architecture later. Does it implement the compressed instruction set?

author: TNorthover
author_rank: Contributor
timestamp: March 01, 2017, 04:12:48 am
content: Quote from: brucehoult on March 01, 2017, 03:55:39 amI decided there'd be even more fun toys to play with if I made them myself. And this specific subforum, just looking to stick my oar in. Good to see you here too!QuoteI'm afraid I don't know that for certain (my MIPS is strictly theoretical), but Google suggests at least some of them do (https://www.microchip.com/forums/m296546.aspxin particular).

author: T3sl4co1l
author_rank: Super Contributor
timestamp: March 01, 2017, 08:41:22 am
content: More examples:8086:Code:INC BYTE PTR [BP+02]Instruction: 3 bytes.Time: 15 + 9 (EA) = 24 cycles.Code:INC BYTE PTR Instruction: 4 bytes.Time: 15 + 6 (EA) = 21 cycles.Not a big difference, but fairly slow overall. This is vastly improved in later models (like 386), so the difference is small. x86 is a very powerful instruction set, but in even later models (highly pipelined, microinstruction, superscalar), the power of that instruction set became a huge burden (i.e., instruction decode takes up a lot of die area, time and power).Z80:Code:INC (IX+02)Instruction: 3 bytes.Time: 6 M-cycles (23 clocks).Code:LD A, (0002)INC ALD (0002), AInstructions: 3+1+3 = 7 bytes.Time: 9 M-cycles (30 clocks).There is no INC (nn) instruction (read-modify-write). With more side-effects, we could also do:Code:LD HL, 0002INC (HL)Instructions: 3+1 = 4 bytes.Time: 6 M-cycles (21 clocks).Huh, that's a typo.Z80 CPU User's ManualUM008005-0205, page 102, M cycles is written as 2, but it's actually 3.So the x86 is fairly indifferent, but this shouldn't be surprising for a quirky CISC like that; and Z80 is more comfortable with pointer arithmetic than (load-store at immediate address).Tim

author: tggzzz
author_rank: Super Contributor
timestamp: March 01, 2017, 09:16:39 am
content: Quote from: cyberfish on March 01, 2017, 01:26:50 amBecause the committee discussed/argued about that for at least a year.Now, if youcancast away constness, how can you guarantee that code designed+compiled+optimised assuming constness will continue to functioncorrectlyalongside code compiled optimised assuming it can ignore that specification.QuoteWhile templates were being "designed" the language designers didn't realise their construction had become so complex that it had become Turing complete, and even refused to believe people that tried to point it out. It was only when someone took that compiler (multimeter), wrote a small valid program (measured voltage), and demonstrated that thecompilation processemitted the sequence of primes (soldered wires), that the language designers woke up and smelled the coffee. I remember thisemerging; seehttps://en.wikibooks.org/wiki/C%2B%2B_Programming/Templates/Template_Meta-Programming#History_of_TMPandhttp://www.erwin-unruh.de/primorig.htmlIf the language designers didn't understand their creation, what chance to expert programmers have(let alone normal programmers). We don't live in a "Lake Wobegone world" where all children are above average! The need for language lawyers in ordinary programming situations is a damning indictment.IMNSHO it is a very bad starting point to use a language where it isprovably impossibleto determine whether avalidprogram will even complete compilation!

author: tatus1969
author_rank: Super Contributor
timestamp: March 01, 2017, 09:41:30 am
content: browsed through the comments and did not see one hard reason why the scope of each and every variable should be as short as possible:When you make everything global, then the compiler has to allocate enough memory to hold all of them simultaneously. But if you restrict yourself to making only those global that need it (need to be static, or need to be accessed from multiple modules), and make all others function ("dynamic") scope, then you will have less total memory consumption in most cases.I am assuming that all dynamic variables are allocated on the stack and freed again after use (valid for most architectures). I also assume that the application has more than one call nesting "branch". Imagine that you call one branch from main(). The machine executes it and eventually reaches the deepest nesting level. At this time, all dynamic variables for this branch are present on the stack. The machine continues to work through that code and eventually returns back to main(). It then has deallocated again all these dynamic variables from the stack. Imagine that it then jumps into and down another branch, again allocating dynamic variables on the stack. This is the important moment: all the new variables share thesamememory as the others from the first branch.Generally spoken, the maximum stack space required is determined by the one of all the independent branches, that has the largest set of dynamic variables. Plus overhead from parameter / return value passing and return address storage. Modern compilers can calculate this value, which gives you the ability to set the stack size right.

author: Kjelt
author_rank: Super Contributor
timestamp: March 01, 2017, 09:51:11 am
content: A lot of good arguments already given, going to add two:- Some processor architectures like the STM8 have a "zero page" RAM of 128 bytes or so that are accessible in a single cycle, all other RAM locations are two or more cycles.So let the compiler use this fast RAM for where it is best used: local variables in for or while loops.- Security: Global variables for all possible variables are a hackers heaven.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: March 01, 2017, 09:51:21 am
content: Quote from: tatus1969 on March 01, 2017, 09:41:30 amI think that in some cases, e.g. PIC8, locals are statically allocated in a pool, the size being determined by the call tree, so the memory usage will only be that needed by the deepest call path. The compiler can figure out how to safely re-use memory that can't be used by multiple procedures at the same time.This is probably going to be more efficient than using a stack, especially on small systems, as all the addresses are fixed at compile time, and avoids the uncertainty of knowing whether the stack allocation is big enough.

author: forrestc
author_rank: Unknown
timestamp: March 01, 2017, 10:27:01 am
content: Quote from: mikeselectricstuff on March 01, 2017, 09:51:21 amThis is exactly what I was going to say, more or less.By declaring locals, the compiler is able to better understand the scope of the variable usage, and re-use the storage space accordingly. In a call tree with no recursion, variables can be statically allocated in memory and the space can be reused across functions which are never active at the same time (i.e. are in different parts of the call tree) - hope that was clear.With recursion, the compiler can switch to a stack-based memory model for those functions which are in the recursive part of the call tree.One other related point is optimizations for speed vs size vs storage space. These should be easier with local variables, as the compiler is free to use registers where appropriate, and memory where appropriate.

author: madires
author_rank: Super Contributor
timestamp: March 01, 2017, 10:42:42 am
content: Another thing to consider when using global variables is the way you organize and access the variables. The best method depends on the addressing commands of the MCU.

author: cyberfish
author_rank: Regular Contributor
timestamp: March 01, 2017, 11:24:39 am
content: Quote from: tggzzz on March 01, 2017, 09:16:39 amI'm glad they spent a lot of time debating a feature that is obviously controversial.const_cast is one of those things that shouldn't be used unless you know exactly what you are doing - most reference materials make that clear. It's for casting away const-ness in a const reference type when you know the object it refers to wasn't const to begin with. There are a few situations where it's useful. Casting const-ness away when the object was actually const results in undefined behaviour, as you would expect.QuoteThat's really not the same at all. They didn't "discover" a new feature. They "discovered" a new use for a feature they designed. It would be equivalent to figuring out that you can use multimeters' sample and hold capacitors to display CPU utilization of a microcontroller by having the microcontroller turn a GPIO on in the active part of an event loop, and off when sleeping. It's cool, but of little practical significance. People discover new ways to use existing things all the time, and I wouldn't say that's a sign of bad design. I would say it's a sign of good design when mechanisms turn out to be more capable than the designers imagined.

author: DJohn
author_rank: Regular Contributor
timestamp: March 01, 2017, 12:44:12 pm
content: Quote from: TNorthover on March 01, 2017, 03:08:52 amIndeed. And sometimes it's not even the ABI. I've worked on a platform on which small structs passed by value would be passed in registers, despite the ABI saying they must go on the stack. The compiler knew that the callee would not be placed in a library (which would have to use the official ABI), and could use its own calling convention.In the case of the non-inlined pass-by-global example on PIC32, it's not necessarily just the extra store and load instructions that are making it so much slower. That code will be storing data to memory, jumping to the function, then immediately loading the data back. What actually happens is that the store happens, then while the data is making its way to the cache (or to main memory if you're unlucky), the jump and load instructions will start. The load will recognise the data dependency and stall until the load has finished. It will then start loading (hopefully from cache this time). But then the next instruction tries to use the data, and stalls again.Caches are fast, but their latency can still be surprisingly high. That's the dirty secret of CPU design: high latency is often the price you pay for high throughput.On 8 bit processors with no cache and few registers, things will of course be completely different. If you care about performance, there is no substitute for knowing your processor and knowing your compiler. Read the disassembly. And trust only the profiler.

author: tatus1969
author_rank: Super Contributor
timestamp: March 01, 2017, 12:49:06 pm
content: Quote from: mikeselectricstuff on March 01, 2017, 09:51:21 amagree, the 8051 is another candidate from which I know this. These architectures do not provide efficient [indirect + offset] addressing schemes, so the compiler designers had to go that route. I like that strategy, but it adds complexity because only the linker can do that optimization,andit needs to knowalldependencies. And that can become a sever limitation of it. As soon as you start using function pointers that are calculated at runtime, it really depends on the compiler/linker cleverness to "see" that dependency. I went into this problem long ago and it really tooke me a lot of time to detect it.

author: tggzzz
author_rank: Super Contributor
timestamp: March 01, 2017, 01:37:14 pm
content: Quote from: cyberfish on March 01, 2017, 11:24:39 amJust so. The problem is that any observed effect can be subtle and is not necessarily visible in the source code, since it is a property of the source code, the compiler version, the compiler flags, the specific processor, and also the data. While one might accept such "random failures" in a generic application, they are often not acceptable in embedded systems, especially anything related to safety.It is insufficient to simply say "you have to use the tool correctly" when in practice it is very difficult to ensure/check that it is used "correctly", and there are alternative tools where no such problem exists.QuoteIt is more than that.Initially thelanguage designersrefused to acceptthat it was Turing complete until someone rubbed their noses in it and they could no longer wish/pretend the "feature" didn't exist.QuoteThat's different. It is entirely acceptable and desirable that tool designers don't anticipate all theusesfor their tool. It is unacceptable that the designers don't understand their own tool.Do you think it is beneficial that valid programs can fail to complete compilation?Do you think it is beneficial that you cannot tell whether valid programs can be compiled?

author: bookaboo
author_rank: Frequent Contributor
timestamp: March 01, 2017, 01:49:20 pm
content: Scrolled through most of this and didn't see one of the major reasons listed yet and that's portability. If you write a function with a few arguments and returning a value it's usually a cut/paste job to port it to another project. Whereas if you use globals you need to make sure there's no clash in variable names between new and ported code.It's also more readable and easier to debug.

author: brucehoult
author_rank: Super Contributor
timestamp: March 01, 2017, 02:16:56 pm
content: Quote from: tggzzz on March 01, 2017, 09:16:39 amC++ templates are far from being the only thing that has accidentally turned out to be Turing complete.Some other examples:- Java generics. Opps! They even knew about C++ templates in advance!- x86 MMU fault handling- Magic, The Gathering- HTML5+CSShttp://beza1e1.tuxen.de/articles/accidentally_turing_complete.htmlAlso: vi key macroshttps://github.com/divVerent/vi-turingAlso: x86, even without using any registers (not even the condition code register and branches)http://mainisusuallyafunction.blogspot.ru/2014/02/x86-is-turing-complete-with-no-registers.html

author: tggzzz
author_rank: Super Contributor
timestamp: March 01, 2017, 03:08:51 pm
content: Quote from: brucehoult on March 01, 2017, 02:16:56 pmI'm not a fan of Java's generics, but I haven't seen any solid evidence they areaccidentallyTuring complete, let alone that the designers (initially) denied that. Mind you, I haven't looked.QuoteSurprising, but not something that a developer could invoke using the tools they use as part of their day-to-day job.QuoteI don't think Magic's objective is to develop programs!As for HTML5+CSS, it doesn't surprise me since that whole area is a mutated mess! Given the net and graphic designers are involved, I'm not sure anyone would notice grossly slow and abnormal behaviourQuoteAsserts rather than proves accidental Turing Completeness.QuoteSure; unsurprising.But it remains that the baroque complexity of C++ baffled even the language designers. That's indicative of something that is out of control.

author: @rt
author_rank: Super Contributor
timestamp: March 01, 2017, 03:20:00 pm
content: Where a function setpixel() is used most in a graphics library, no matter whether drawing lines or circles,and no matter their arguments, setpixel is going to be called the most by those functions, so there should be a differencethat could be measured by only changing that function to pass arguments setpixel(int x, int y);Then if you drew a whole bunch of lines and circles, and measured the time with hardware timer,is just that one change to code a fair measurement of anything?

author: cyberfish
author_rank: Regular Contributor
timestamp: March 01, 2017, 03:26:47 pm
content: Quote from: tggzzz on March 01, 2017, 01:37:14 pmIt does not depend on the compiler, hardware, or data at all. Correctly written code will work with any correctly written compiler.If you cannot guarantee that the object is not const, you cannot use const_cast. Practically, that means const_cast cannot be used except in very specific cases. The only such specific case I have seen is to provide an overloaded accessor function that returns a const reference if "this" pointer is const, and a non-const reference if it isn't. It saves code duplication in this case, and the behaviour is completely defined, and will work with any standard-compliant compiler. This is the only use of const_cast I have seen in high quality code.Code:class A {public:const int& f() const {/* do a bunch of stuff */return x;}int& f() {const &A const_this = *this;return const_cast<const A*>(const_this->GetX());}private:int x;}Yes, it's convoluted, and shouldn't be used unless you know exactly what you are doing.This problem doesn't exist in alternative tools, because alternative tools don't allow you to do this at all. You have to write two different functions if you want a const and a non-const version. You can do that in C++ too if you want (and people who don't know how const_cast works should).Java solves it by not supporting const (final) references. You can disallow assigning another object to the reference, but not make the object itself final. If you don't use const references in C++ (which makes things safer, without adding any overhead), you won't have this problem either.QuoteEven if that's true, it's something that happened decades ago. Again, I evaluate language features by their merit, and not by where/how it came about.QuoteNo, but I think it is beneficial to have a powerful meta-programming system that allows people to do things like compile-time optimization of expressions in a linear algebra library that is not possible in languages without a meta-programming system (http://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html).If you have a powerful meta-programming system, those properties are unavoidable side effects.And I think the fact that you can abuse it in a way to write a valid program that fails to compile is an acceptable downside to that, because I am not going to do that.

author: TNorthover
author_rank: Contributor
timestamp: March 01, 2017, 03:54:57 pm
content: Allowing casting constness away is also a necessary for C compatibility. And the template horrors are gradually being reduced by constexpr in newer standards.

author: tggzzz
author_rank: Super Contributor
timestamp: March 01, 2017, 04:18:52 pm
content: Quote from: cyberfish on March 01, 2017, 03:26:47 pmWell, doh. That's a trivially true statement since it depends on the definitions of "correct". From listening to echoes of what happened in and after some of the standardisation attempts, that was non-trivial since the committee members disagreed about what some of the wording meant. Without such agreement, any definition of "correct" is moot. And then similar things happened when the commercial compiler writers asked what sections of the standard meant, because there were, apparently and remarkably, conflicting sections.I also remember somewhere around 2005 somebody triumphantly announcing the first complete compiler. Now I forget whether it was for C or C++, but since it was 6yearsafter the standard was fixed, it is plausible that it was for C99. Sorry I can't be more specific.However, looking athttps://en.wikipedia.org/wiki/C99#Implementationsit appears that even GCC doesn't fully implement C99 after 15 years!Of course mere users also have to try and figure out which compilers do/don't "correctly implement" the language - which is an impossible task.QuoteAgain, you are saying "in correctly written code you get correct results", carefully avoiding the issue of whether it is practical to assume that all the code in your system is "correct".QuoteYes, alternative tools don't allow such things, principally because they have a simpler better foundation where such things aren't necessary.QuoteComparing with Java isn't helpful in this case, for the above reason.QuoteUnderstanding history is a useful way of determining where skeletons arestillconcealed, or alternatively of determining that the skeletons have been properly removed.QuoteI agree.But those benefits (and many other) should be embodied either in a library, or in a well-defined special-purpose language - possibly one that is converted to plain-vanilla C/C++ for compilation and execution.Accidentally including them in a general purpose language is a specification and design smell.QuoteI'm sureyouand your compiler always produce correct code. Unfortunately most people don't even realise that they are subtly screwing up - and C/C++ baroque complexity is part of that problem.

author: tggzzz
author_rank: Super Contributor
timestamp: March 01, 2017, 04:21:49 pm
content: Quote from: TNorthover on March 01, 2017, 03:54:57 pmJust so. Of course that amelioration presumes that compiler writers and developers will correctly implement and use such features.Unfortunately there is a mass of important code in the wild that will never be rewritten, so the current problems will remain for the foreseeable future.

author: cyberfish
author_rank: Regular Contributor
timestamp: March 02, 2017, 04:33:03 pm
content: Quote from: tggzzz on March 01, 2017, 04:18:52 pmYes, standards as big as the C++ standards will inevitably have ambiguity in places. They are to be fixed in future standards. Many things were cleared up in C++11 for example. The C++ standard is not perfect, and that's why it's still being revised.QuoteAnd then you have languages like Objective-C or Python that don't even have a standard, only a reference implementation. Obviously in this case you'll have a perfect implementation and a perfectly-defined language, by definition! In theory that means all other implementation must also reproduce bugs in the reference implementation.QuoteAll compilers for all languages have bugs. Are you claiming otherwise?QuoteCan you assume that all the code in your system involved in Obj-C compilation are correct? If you can, so can I. All C++ standard libraries included in major compilers have been extensively tested, and finding a bug in the field is extremely rare.QuoteLike in Objective-C, by basically making const useless?What, concretely, are you referring to?QuoteJava can definitely be made safer by having a good const ref implementation.You have an ArrayList of large objects, and you want to let the caller access one element, read-only, without making any copies. How do you go about doing that?You can't. You have to either trust the caller to not modify the reference you return, or make a copy.QuoteThe language is fully defined by the current standard. All the skeletons are listed, no matter where or how they came about.QuoteTemplate metaprogramming IS mostly done in libraries (like Eigen). You will rarely see that in application code.Well, yes, the special-purpose language is the template language. The compiler does convert it to plain-vanilla C++ for compilation.Would it be better if it was just called a different name and described in a separate document?QuoteYes, C++ is difficult to learn. I don't think anyone is arguing otherwise.It's a powerful language that can be used to write very elegant (and at the same time, high performance) code by proficient programmers, but it does require more training than most other languages.Still, there are at least a million C++ developers in the world better than I am, and I can already use C++ effectively and write more elegant code than I can in any other language. So learning C++ to a very practically useful level is far from impossible.

author: @rt
author_rank: Super Contributor
timestamp: March 03, 2017, 06:42:20 am
content: This will draw two identical patterns in the same y position.It would appear the top one will save significant program memory.For the extra memory, will the second one be faster?I’m aware you could loop through these values in an array of indexes,and there are a number of other ways to save memory on it.Code:x = 13; y = 39; setpixel();x = 114; setpixel(); // global y is already 39.x = 12; y = 40; setpixel();x = 115; setpixel();x = 11; y = 41; setpixel();x = 116; setpixel();x = 11; y = 42; setpixel();x = 116; setpixel();x = 11; y = 43; setpixel();x = 116; setpixel();x = 11; y = 44; setpixel();x = 116; setpixel();x = 11; y = 45; setpixel();x = 116; setpixel();x = 12; y = 46; setpixel();x = 115; setpixel();x = 13; y = 47; setpixel();x = 114; setpixel();Code:setpixel(13,39);setpixel(114,39);setpixel(12,40);setpixel(115,40);setpixel(11,41);setpixel(116,41);setpixel(11,42);setpixel(116,42);setpixel(11,43);setpixel(116,43);setpixel(11,44);setpixel(116,44);setpixel(11,45);setpixel(116,45);setpixel(12,46);setpixel(115,46);setpixel(13,47);setpixel(114,47);

author: janekm
author_rank: Unknown
timestamp: March 03, 2017, 07:10:33 am
content: Quote from: @rt on March 03, 2017, 06:42:20 amYou seem to be forgetting that optimising compilers exist.Your first example compiles to the following with ARM gcc 5.4.1 with -Oshttps://godbolt.org/g/c8O0nw(-O3 is better in this case, but still worse than the version with parameters):Code:setpixel():ldr r2, .L2ldr r0, ldr r1, [r2, #4]mov r2, #320ldr r3, .L2+4ldr r3, mla r3, r2, r0, r3mvn r2, #0strb r2, [r3, r1]bx lr.L2:.word .LANCHOR1.word .LANCHOR0main:stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, lr}mov r3, #39mov r8, #13mov r7, #114ldr r4, .L6mov r10, #12stmia r4, {r3, r8}mov r9, #115bl setpixel()str r7, [r4, #4]bl setpixel()mov r3, #40mov r6, #11mov r5, #116stmia r4, {r3, r10}bl setpixel()str r9, [r4, #4]bl setpixel()mov r3, #41stmia r4, {r3, r6}bl setpixel()str r5, [r4, #4]bl setpixel()mov r3, #42str r6, [r4, #4]str r3, bl setpixel()str r5, [r4, #4]bl setpixel()mov r3, #43str r6, [r4, #4]str r3, bl setpixel()str r5, [r4, #4]bl setpixel()mov r3, #44str r6, [r4, #4]str r3, bl setpixel()str r5, [r4, #4]bl setpixel()mov r3, #45str r6, [r4, #4]str r3, bl setpixel()str r5, [r4, #4]bl setpixel()mov r3, #46str r10, [r4, #4]str r3, bl setpixel()str r9, [r4, #4]bl setpixel()mov r3, #47str r8, [r4, #4]str r3, bl setpixel()str r7, [r4, #4]bl setpixel()mov r0, #0ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, lr}bx lr.L6:.word .LANCHOR1mem:.word 1073745920y:x:Your second example becomes this with the same optionshttps://godbolt.org/g/O4qx6Z:Code:setpixel(int, int):ldr r3, .L2ldr r2, mov r3, #320mla r1, r3, r1, r2mvn r3, #0strb r3, [r1, r0]bx lr.L2:.word .LANCHOR0main:mvn r2, #0ldr r3, .L5ldr r3, add r3, r3, #12288strb r2, [r3, #205]strb r2, [r3, #306]strb r2, [r3, #524]strb r2, [r3, #627]strb r2, [r3, #843]strb r2, [r3, #948]strb r2, [r3, #1163]strb r2, [r3, #1268]strb r2, [r3, #1483]strb r2, [r3, #1588]strb r2, [r3, #1803]strb r2, [r3, #1908]strb r2, [r3, #2123]strb r2, [r3, #2228]strb r2, [r3, #2444]strb r2, [r3, #2547]mov r0, #0bx lr.L5:.word .LANCHOR0mem:.word 1073745920

author: brucehoult
author_rank: Super Contributor
timestamp: March 03, 2017, 08:27:43 am
content: Brilliant!!

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: March 03, 2017, 09:34:23 pm
content: Quote from: @rt on February 27, 2017, 02:45:35 pmHe was right. The purpose of using a HLL is so the programmer can concentrate on program structure and let the compiler worry about implementation.QuoteIn this trivial example it may be more efficient to do setpixel(x,y)butit could be a different story if the coordinates are variable.Write code that reflects what you are trying to do, not contortions that you think might help the compiler to do a better job. If you want to display a fixed array of pixels then write appropriate code to do that. Hard-coding an image into a sequence of instructions is just awful programming style, whatever language you use.If at the end you find that the compiled code is too inefficient,thenconsider what to do about it. But don't create a hack unless you are willing to accept the consequences (obtuse and unmaintainable code, lack of portability, compiler-specific results etc.). Most modern CPUs are optimized for C, and modern compilers are pretty good at optimizing away the 'fluff'. If you need to squeeze the absolute maximum out of a low-end chip and the compiler isn't up to it then program in assembler - then you will have complete control!

author: T3sl4co1l
author_rank: Super Contributor
timestamp: March 04, 2017, 01:43:31 am
content: Here's an optimization you probably won't get from the compiler: read it as an array.In both above examples, the values are used in sequence, and not assigned to any memory (beyond a single pair of local variables, or registers). That incurs a lot of MOV reg,imm instructions, which usually aren't very efficient: you have to encode the instruction itself, plus the data.If instead we put the values in an array (usually a const array), then they can be "streamed" out with an auto-increment pointer (if such operations are available on the platform, that is). So it turns into:Code:MOV ptr,&points_arraycycle:MOV reg1,[ptr+]MOV reg2,[ptr+]CALL PlotPointLOOP cycleRolling things up into a loop saves code space, and the array can be inlined with code (Von Neumann architecture) or stuffed away in the data segment. It probably executes slower, though -- loops being what they are.On Harvard architectures, this saves space nicely. AVR for example. Loading registers is one thing, but having the compiler do it is doubly worse: C only knows how to read and write from SRAM. You have to go out of your way to implement a const array in program memory, and you can't just pass it around, you have to use stub functions to read it into SRAM. Whereas you can write something like,Code:movw Z,offset(points_array) << 1cycle:lpm r16, z+lpm r17, z+rcall PlotPointbrne cycleTakes up 6 words of PROGMEM, and only one byte (half word) per uint8_t sized parameter (two byte minimum).Of course making the optimization of in-register parameter passing, for both cases. Pushing and popping things only adds overhead, but it's a constant adder for both examples.Tim

author: Feynman
author_rank: Regular Contributor
timestamp: March 04, 2017, 08:43:08 am
content: Quote from: @rt on February 27, 2017, 02:45:35 pmWhy shouldn't "not making a mess" be a real reason? That's probably the most siginificant reason as a professional.

author: Jeroen3
author_rank: Super Contributor
timestamp: March 04, 2017, 01:01:17 pm
content: Quote from: T3sl4co1l on March 04, 2017, 01:43:31 amCompilers use pointer arithmetic all the time. They're not made as dumb as you think.They can even re-order non volatile statements to fit the arithmetic.

author: brucehoult
author_rank: Super Contributor
timestamp: March 04, 2017, 01:12:21 pm
content: Quote from: Jeroen3 on March 04, 2017, 01:01:17 pmI've never seen a compiler take a series of hand-written statements, identical except for the literals, and extract the literals into an array and use a loop. And I write compiler optimizations for a living :-)You *could* do it. It's just the inverse of unrolling, which compilers do all the time. But it would be quite expensive to find, and would only very seldom find code on which it could do it. And then you just made the program slower, so it's only something you'd do when optimising for size, which is also not the common case.

author: Jeroen3
author_rank: Super Contributor
timestamp: March 04, 2017, 02:09:28 pm
content: I was looking in some 8051 listing. Code fetches a pointer to a global.Next 5 different globals are accessed by adding to the pointer.It doesn't put them in a loop, but it skips the pointer fetch.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: March 04, 2017, 03:25:37 pm
content: Quote from: Jeroen3 on March 04, 2017, 01:01:17 pmIt's quite standard to use pointer + offset to refer to a stack frame, for example. That's easy to set up, though: the variables are allocated in specific locations, they can be used independently -- and the offset can be called out independently, thanks to instructions like MOV reg,[reg+imm].It's not obvious that a compiler would know how to resolve that into an array access: one where the pointer itself is moving. That would complicate other stack accesses, at the very least: if you use one MOV reg,[reg++] instruction, now all subsequent offsets have to be one less, and so on. If it's inside a loop, the offsets vary from time to time, and you can't make that assumption at all. Then you need to use a new pointer register (which is available on many platforms, at least).I don't know that compilers will ever create a loop to optimize for size; speed is the most common optimization after all, and it's nice that unrolling a loop is a much more well-defined problem than rolling one up is!Bruce seems to have confirmed my suspicion, so at least I'm not completely wrong for once.Tim