Prefix or postfix increment in 'for' loops - Page 1
Forum section: electronics / general

author: hamster_nz
author_rank: Super Contributor
timestamp: October 08, 2020, 10:10:24 pm
content: I've seen both, they are identical and just wondering what you use and why.Me? I tend to avoid prefixed increment, I guess I just to want it the variable to be on the left hand side...

author: AntiProtonBoy
author_rank: Frequent Contributor
timestamp: October 08, 2020, 11:05:30 pm
content: Quote from: hamster_nz on October 08, 2020, 10:10:24 pmNot always:Code:int i = 5;int a = ++i;int b = i++;assert( a != b );In C++, therecouldbe also performance implications if you overload the aforementioned operators for your classes. If your class is big, the prefix operator requires only a reference to the object to be returned, whereas the postfix version must return a temporary object by value.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: October 08, 2020, 11:39:18 pm
content: Use whatever is works for you.One of my favorites is:Code:for ( uint8_t Mask = 1; Mask; Mask <<=1){ ...}also compare these:Code:int func_a( void) {int Num = 42;return ++Num;// return Num++; // Now try this for a change...}... which is a variant of the same of antiprotonboy's post.And another hint, If they were the same, they would not both be defined in C.The difference can be particularly usefull when indexing through arrays.

author: Cerebus
author_rank: Super Contributor
timestamp: October 08, 2020, 11:57:56 pm
content: Quote from: AntiProtonBoy on October 08, 2020, 11:05:30 pmI think the 'they' there referred to the whole loops,notto the operators which clearly deliver different result values. The loops are functionally identical whichever you use, pre- or post- increment, as the returned value from the increment operation is discarded.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 09, 2020, 12:17:17 am
content: Quote from: Cerebus on October 08, 2020, 11:57:56 pmOf course. The question was strictly limited to this use case in the original post. I'm sure hamster knows the difference when you *use* the value of a pre- or post-incremented expression. But it's very common in those threads to get off-topic very quickly.Now even if you don't use the value itself, could this make a difference? Certainly not in C, but I guess in some object-oriented languages, if the variable is actually an object and the language allows defining operators for objects, then certainly the result *could* be different, although defining them differently (as far as the side-effect goes, which is incrementing the "value" in some way, so apart again from the return value) would probably not make any sense.Back to what the original question most likely was: yes I've seen both forms in the final clause of for loops in C. I personally use the former, as it looks more natural, and thus more readable IMO.I remember having heard of a rationale from people using the '++i' form *in this context* (so again with the value itself discarded, just saying it again but making sure no one will be again tempted to reply with examples in which the value of the expression is used), but it was probably so smart and so relevant that I don't even remember it.And say, even in C++, if the variable is of an integer (or pointer) type, which is most likely what the OP was thinking of, there is absolutely no difference.

author: maginnovision
author_rank: Super Contributor
timestamp: October 09, 2020, 01:28:40 am
content: I do not have strong feelings either way so I checked my code. I do prefix.

author: Rick Law
author_rank: Super Contributor
timestamp: October 09, 2020, 01:53:33 am
content: I formed my habit of using "++i" instead of "i++" -- from older (and smaller code-size) compilers without good optimization.++i means increment first then use the value, so ++i typical leaves the new value in register and ready to be use by the loop-condition test, whereas i++ leaves the old value (use old value then increment) in register and requires another memory read.Of course, compilers have much better optimization now. But my habit is already form. So it is purely fossilized habit that I continue to favor ++i over i++. Besides, it makes sense to me - I am using the value in the condition-test after the increment.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: October 09, 2020, 02:11:45 am
content: I use postfix, largely by habit.On a related subject, it's preferred to put constants on the left-hand side of a conditional: this errors out if you typo an equality in. "if (7 == x)" is correct, "if (7 = x)" errors, "if (x = 7)" works silently and not as you might've expected it to! (Modern compilers do warn of assignment-in-conditional; but sometimes it is intended as well.) I tend...notto do this, again largely by habit.(Fortunately, I'm no SW dev, so I can afford to make mistakes in my own projects.)Tim

author: ledtester
author_rank: Super Contributor
timestamp: October 09, 2020, 02:14:42 am
content: The Google C++ Style Guide says:Quotehttps://google.github.io/styleguide/cppguide.html#Preincrement_and_PredecrementNote that if you are using C++ and define iterator semantics for a type I think you'll always want to use pre-increment because the compiler might not be able to optimize away unnecessary increment operations. For that reason I would get in the habit of using pre-increment.

author: magic
author_rank: Super Contributor
timestamp: October 09, 2020, 07:17:44 am
content: Quote from: T3sl4co1l on October 09, 2020, 02:11:45 amIt's rare, thankfullyBasically, a nonissue that autists obssess about

author: janoc
author_rank: Super Contributor
timestamp: October 09, 2020, 08:20:47 am
content: The main reason why prefix is used and recommended in situations where everything else would be equal (i.e. you don't specifically care about the order of operations), is that postfix will make a temporary copy of the value.Probably not much of an issue in C with something like an integer and a good compiler that will optimize the temporary out but it could be pretty bad if you use that operator in C++ on something that is not a simple data type. C++ allows operator overloading and making a copy of a large class/struct, especially in a loop like this, is going to be terribly inefficient.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: October 09, 2020, 09:21:40 am
content: There needs to be a third option on the poll.Seriously modern compilers are really really smart so this is a non questionIf you want some reading tryhttps://www.drdobbs.com/architecture-and-design/efficiency-versus-intent/228700184

author: magic
author_rank: Super Contributor
timestamp: October 09, 2020, 09:31:58 am
content: Yes indeed.I'm not quite old enough to remember it personally, but I always assumed that this practice was popularized by the C++ crowd when iterators were introduced. Those, being fully featured classes with methods and stuff, make postincrement somewhat harder to optimize than with basic types.C programmers generally don't bother. And heck, even C++ is called C++ rather than ++C, despite the value of the former expression still technically being just C

author: PKTKS
author_rank: Super Contributor
timestamp: October 09, 2020, 11:53:11 am
content: Las two comments HOLD TRUE.Today this is no longer even for consideration.*UNLESS* obviously you are compiling code using olderthings like Borland 3,4/5 or even 6 or even the watcom (modernopenwatcom as well)..With the advent of full register renaming re-indexing andFULL RISC opcode micro firmware it is mostly a waste of timethinking *what* a modern compiler (say GCC >=9.x) would doinside the loops.Ancient alternative were using options line "unroll-loops" andoptimization specific target the CPU ... but these alternativesonly serves to generate UNPORTABLE CODE among modern CPUs.I always keep it safe using i++ and letting the compiler optimizerdo the less risky thing .. as the full loop may be interrupted in modern threads.hard to guess.Paul

author: janoc
author_rank: Super Contributor
timestamp: October 09, 2020, 02:06:45 pm
content: Guys, if you are talking about compilers and optimizations, you are making some very big assumptions.Are we dealing with C and/or iterating POD (plain old data) types? Possibly - in that case sure, be my guest. In C and with integral types the cost of the extra copy is going to be negligible and a modern compiler will most likely notice you aren't using the value for anything and optimize the temporary out.However, once you are in C++ (and the OPdid not specify which language is it for!) and are dealing e.g. with iterators or containers that define these operators (both are commonly used in for loops), you can't rely on the compiler optimization to save your bacon!There is a reason why a large part of the C++ standard deals with things like references, move semantic, universal references - all that is to avoid copying large data structures which could end up being pretty expensive. If this was so easy for compilers to optimize out there would be no need for all that complexity.

author: PKTKS
author_rank: Super Contributor
timestamp: October 09, 2020, 02:38:35 pm
content: Not much of an issue.C++ is C with classes.Unless you will NOT USE at all C constructs justplain templates or overloaded classes...You will do a regular C "for" construct in both.Notable exception in above comment is that ME andprobably several other old school die hard bad habitsof direct memory address are really a nasty problemEXAMPLE:some_lvalue = & some_pointer( ++dma )WTF is not under case likesome_lvalue = & some_pointer( dma++ )or variants to auto increment indexed pointers:auto_addr = ++ * some_pointer( dma )WTF not even close toauto_addr = * some_pointer( dma ) ++I am used to write these and mostly I translatethat shit to ASM to avoid MODERN CRAPPY MULTI-CPU issuesin small words:you need to make dead sure the pointer will accesslocation prior or post the indexed increment takes placeand letting just the C operator precedence will lead thiscode in issues when interrupted in sensitive contexts.Paul

author: Howardlong
author_rank: Super Contributor
timestamp: October 09, 2020, 03:43:51 pm
content: Personally by habit I've used i++ for decades as my go to option.And my goto option is irrelevant, I've never once used goto in C.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: October 09, 2020, 03:51:32 pm
content: Goodness.. this one is going to run and run...Here's an interesting take...https://google.github.io/styleguide/cppguide.html#Preincrement_and_PredecrementBasically it's us old school traditionalists that insist on using post increment... we're just dinosaurs.Obviously either works and the LVAR is thrown away when the operation is invoked so pre and post are indistinguishable functionally here.TGIF

author: maginnovision
author_rank: Super Contributor
timestamp: October 09, 2020, 04:40:54 pm
content: Quote from: Howardlong on October 09, 2020, 03:43:51 pmI looked at my first c game I wrote when I was 11 or so. It was a single main function full of labels and gotos. I thought it was funny and showed it to some people to try and follow it.

author: janoc
author_rank: Super Contributor
timestamp: October 09, 2020, 05:00:37 pm
content: Quote from: PKTKS on October 09, 2020, 02:38:35 pmSorry, I guess you haven't written much C++ (not C compiled with a C++ compiler) code, have you?Quote from: PKTKS on October 09, 2020, 02:38:35 pmThat's just a tautology. Aka "If you write C code in C++ you will be writing regular C for".Quote from: PKTKS on October 09, 2020, 02:38:35 pmThat's just plain bad code that wouldn't pass a code review, full stop. Taking addresses of temporaries by chance? And I feel you are conflating several unrelated issues there too, given your comment on "MODERN CRAPPY MULTI-CPU issues".Whether you are using prefix or postfix increment has no bearing on any "multi-CPU issues" unless you are grossly ignoring basic concurrency issues like not sharing state between two threads (or an interrupt handler and some other code) without synchronization. But that is just poor code, nothing else.

author: magic
author_rank: Super Contributor
timestamp: October 09, 2020, 05:43:02 pm
content: Quote from: janoc on October 09, 2020, 02:06:45 pmIt kinda depends on how complex the iterator is. I have a (never verified) suspicion that in STL containers most iterators consist of just a pointer to the given element or null. In some calling conventions (like x86-64 Linux) returning by value a structure of up to two 64 bit words from a class method or other function goes through registers RAX, RDX and is essentially free. But on 32 bit Linux it was never like that and no idea about other systems like Windows.And if the operator++ method is sufficiently trivial (think: list iterator) it will very likely get inlined and then elimination of an unused temporary is a trivial optimization that any modern C++ compiler will do at non-zero optimization level.But you are making a gamble relying on any of that unless you know what you are doing and writing a strictly platform-specific code.

author: S. Petrukhin
author_rank: Super Contributor
timestamp: October 09, 2020, 08:00:34 pm
content: for i:=1 to 10 do beginend;

author: Howardlong
author_rank: Super Contributor
timestamp: October 09, 2020, 09:48:35 pm
content: Quote from: S. Petrukhin on October 09, 2020, 08:00:34 pmCrikey, that takes me back, I earned a crust writing that stuff back in the mid 80s. Pascal on a Pr1me, then Turbo Pascal and Microsoft Pascal on the PC. I was quite the fan boy of Pascal. Turbo Pascal, with its IDE and super fast in-memory edit-compile-run paradigm was a massive turning point in programming development, it showed what could be done, and Microsoft took a long time to catch up.I remember Microsoft coming out with Programmer's Workbench (PWB), possibly the biggest single hinderance to software development there ever was. a huge bloated disk intensive monstrosity that tried to cover all bases, something that it did indeed achieve, just really, really badly.We used to do unit testing in Turbo C because it was much faster (we made our source code compatible between compilers to aid development time), and then system test & beyond was in in Microsoft C.

author: indeterminatus
author_rank: Contributor
timestamp: October 09, 2020, 10:19:39 pm
content: My take on this (as SW engineer): consistency. Pick one and stick with it, at least within a given project. Do not pay too much attention to "potential performance problems" with either one of them. If that really is your biggest concern, you've already done an extra-ordinarily good job. Most of the times, the increment of a loop variable will make no difference.As far as performance optimization goes: always measure, never guess. Do not optimize prematurely, it may even have an adverse effect (you'd be surprised how smart compilers actually can be!). Optimize on macroscopic level (pick appropriate algorithms and data structures), taking the boundary conditions into account; bubble sort, while scaling terribly, might actually be fast enough if you know you're only ever dealing with a hand-ful of elements. Profile, profile, profile. And then profile some more. Pick the hotspots and remove them. Then profile again to verify improvements.More often than not, the clarity of the code should be your number 1 priority. It should be immediately obvious what the code is doing and why. When reading the code, the moment you have to stop and think what the hell is going on, its structure is bad and needs to be changed. Never waste the time of the person reading your code -- this is very rude. Which closes the loop: consistency. If you use post-increment here and here and here, and at one place you pre-increment, what you're basically doing is forcing the reader of the code to think about the difference and why that would be important. Time and concentration budget that could've been spent more productively, going to waste.This may sound a bit harsh, but I've seen many programs written by brilliant mathematicians and physicists, that are a nightmare to read and understand, let alone adapt. If you think about it, the "adapting" part is the main trait of the software. If it cannot be easily changed, it is no longer "soft", and thus misses its point. Please, don't be clever.

author: indeterminatus
author_rank: Contributor
timestamp: October 10, 2020, 08:57:03 am
content: Quote from: PKTKS on October 10, 2020, 08:23:09 amIn case you're referring to my reply, no insult was intended, and I do most certainly not want to discourage exposing yourself to criticism. It takes a lot of courage to post bits of work; it's easy to criticize, it's very difficult to create something. This is an unfair bias that (unfortunately) often leaves a bitter taste after review.

author: PKTKS
author_rank: Super Contributor
timestamp: October 10, 2020, 09:50:17 am
content: Quote from: janoc on October 09, 2020, 05:00:37 pm2 cents ...I am not offering any of my code for anyone here to reviewin particular to satisfy bad egos envy and child like folly insultsThat folk probably thinks he is that full of shitPaul

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 10, 2020, 08:25:48 pm
content: for (i = 0; i < count; i += 1)

author: S. Petrukhin
author_rank: Super Contributor
timestamp: October 10, 2020, 09:21:38 pm
content: Quote from: Nominal Animal on October 10, 2020, 08:25:48 pmfor (i=0; i<count; i=i+1)

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 10, 2020, 10:55:16 pm
content: Actually,for (double d = dmin; d <= dmax; d = nextafter(d, HUGE_VAL))is occasionally useful (in C99 or later) to examine alldoublevalues betweendminanddmax, inclusive. It's also one of the examples where using a floating-point loop variable is perfectly acceptable.

author: S. Petrukhin
author_rank: Super Contributor
timestamp: October 11, 2020, 12:27:07 am
content: Quote from: Nominal Animal on October 10, 2020, 10:55:16 pmThe for loop in all languages is iterative, i.e. it repeats by counter. The C language historically did not have this loop, and with all its inherent perversity, this macro was muddied, essentially writing the while loop in one line.

author: brucehoult
author_rank: Super Contributor
timestamp: October 11, 2020, 12:28:43 am
content: Quote from: Nominal Animal on October 10, 2020, 10:55:16 pmWith IEEE FP you can do this with just an integer loop and ++i and bit cast the integer to FP, as long as you're not trying to iterate between a negative number and a positive number (or the reverse).

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 11, 2020, 05:20:34 am
content: Yep; in fact, if integer and floating point byte orders are the same, IEEE Binary32 and Binary64 floating-point types data interpreted as an integer will have the same order as the values, if one inverts the sign bit when the sign bit is clear, and all bits when the sign bit is set. (Darn, I always get that wrong the first time.) This is useful for implementing floating-point radix sort for huge data sets, so you can sort those in linear time – although the constant time factor is so large Quicksort et al. will be faster unless you have tens or hundreds of millions of elements or more; depends a bit on the radix sort implementation (bits per pass) and CPU core cache behaviour.

author: Syntax Error
author_rank: Frequent Contributor
timestamp: October 11, 2020, 08:47:50 pm
content: Loops, I most often use for ( i = 0; i < count; i++ ), unless it's a zero indexed array when obviously...Code:for ( i = 0; i < array.count - 1; i++ )Except darn LUA where arrays index from 1, go figure?But is the OP's loop functionally the same as these code snippets (your I.T.L. homework question btw)...Code:i = 0repeat { i++; // stuff...; } until ( i < count )i = 0repeat { // stuff...; } until ( ++i < count )A moot point when...Code:FOR i = 0 TO count STEP 1NEXT iJust one thought to really upset the interpreter nerds though...Code:FOR i = 0 TO count STEP 0.000000000000000000000000001NEXT iNow hop backwards, just for fun...Code:for ( uint32_t i = count; i != -1 ; --i )* Noobs, can you spot the syntax error here?Or just geek out with...Code:i = -1; while ( ( ++i < count ) ? true : false ; ) { // stuff... }Yeh, it's like this yawl

author: AntiProtonBoy
author_rank: Frequent Contributor
timestamp: October 11, 2020, 11:37:42 pm
content: Quote from: PKTKS on October 09, 2020, 02:38:35 pmDisagree. They are distinct languages and should not be conflated in any way. The C++ should be treated as a unique language that in a sense offers a C compatibility layer in syntax and in the ABI.QuoteYeah stuff like that should never pass code review.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 12, 2020, 08:25:58 am
content: I would recommend this one relying on UB:Code:#include <stdio.h>int main(){for(int i=0; i<7; i += i-- - i++)printf("asdf\n");return 0;}Code:hrst@Siwa:~$ gcc -Wall t.ct.c: In function ‘main’:t.c:5:32: warning: operation on ‘i’ may be undefined [-Wsequence-point]for(int i=0; i<7; i += i-- - i++)~^~hrst@Siwa:~$ ./a.outasdfasdfasdfasdfasdfasdfasdf

author: Syntax Error
author_rank: Frequent Contributor
timestamp: October 12, 2020, 08:50:15 am
content: Quote from: Siwastaja on October 12, 2020, 08:25:58 amTrickyWhat about i+= --i - --i

author: golden_labels
author_rank: Super Contributor
timestamp: October 12, 2020, 01:35:09 pm
content: hamster_nz:Unless you are really speaking about a wide range of languages, consider specifying which one the question is about. They really are different.Ipreferpre-increment for clarity while merely incrementing, as it does literally what I am wishing to do: it increments. Post-increment usually means “increment, but also retain the previous value and return it”, which — if used the way you have shown — goes into “increment, but also retain the previous value and return it, but actually ignore it.” I see it as unneccessary complexity to think about that additional step.They are also not identical even in that particular code, at least not in every language. For example in C++ an overloaded postincrement operator may store the value, which is not really needed. The compiler may remove in many cases, but this is not guaranteed.Doctorandus_P:In which language? In C this is undefined behaviour, because you are left-shifting asignedvariable beyond its range.(1)Note that usinguint8_tis irrelevant here: it only tells compilers to perform some additional operations.(2)____(1)C99, C11: 6.5.7§4.(2)Which will be optimized out in any sane compiler nowadays, but this is not guaranteed.

author: PKTKS
author_rank: Super Contributor
timestamp: October 12, 2020, 02:01:44 pm
content: Quote from: AntiProtonBoy on October 11, 2020, 11:37:42 pmYou will be disappointed on how frequently you will findwhat you call "stuff like that" in low level code.And disappointed one more time when you finallyrealize that C++ is C with classes

author: NorthGuy
author_rank: Super Contributor
timestamp: October 12, 2020, 03:33:46 pm
content: In this particular context, the value of the expression is discarded, only the side effect (++) is executed, so there's absolutely no difference.In practical terms, the compiler will probably keep the loop variable in a register on most CPUs (unless the loop is too long), so the operation is actually an increment of the register.More importantly, I don't think you should ever worry about such things. The whole point of using C is to outsource the code generation to the compiler. So, you either trust the compiler, or you don't and write your own assembler.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 12, 2020, 03:55:05 pm
content: Quote from: Syntax Error on October 11, 2020, 08:47:50 pmAre you sure?... Maybe if you were using "<=" instead of "<". Or if you were accessing array[i + 1] somewhere inside your loop.Quote from: Syntax Error on October 11, 2020, 08:47:50 pmLUA doesn't really have "arrays" to begin with. It has "tables", which are very flexible and can be actually indexed any way you want. You can use tables as "arrays" in LUA, depending on how you build the tables. The default indexing will then be 1,2, ..., but that's just a particular case of using tables. There are several ways in LUA to iterate through a table, most of them not requiring actually knowing what the specific indices are, as opposed to C. And when you do need to know, you can actually specify the indices yourself (which are "keys") instead of letting LUA choose them for you.That little digression apart, the most usual way IMO to deal with "arrays" starting at index 0 or 1 is just:Code:for (i = 0; i < count; i++)andCode:for (i = 1; i <= count; i++)respectively. That's pretty basic maths.

author: Syntax Error
author_rank: Frequent Contributor
timestamp: October 12, 2020, 04:51:25 pm
content: Quote from: SiliconWizard on October 12, 2020, 03:55:05 pmWell spotted. <= not < .This is why we need code reviews (it stops planes from crashing)Iterating LUA tables, 'in pairs' gives both the value/key and an index.

author: indeterminatus
author_rank: Contributor
timestamp: October 12, 2020, 09:15:51 pm
content: Indeed, common mistakes. Actually, it's one of the two most difficult problems in computer science:Naming thingsCache invalidationAvoiding off-by-one errors

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 12, 2020, 10:11:36 pm
content: /* ^ Fix later, too busy right now. Comments are for those who can't code. */

author: AntiProtonBoy
author_rank: Frequent Contributor
timestamp: October 13, 2020, 02:56:56 am
content: Quote from: PKTKS on October 12, 2020, 02:01:44 pmSo? It doesn't mean it's okay.QuoteModern C++ is conceptually very different from "C with classes", looking at a language perspective: RAII, templates, lambdas, ranges, iterators, exceptions, polymorphism, inheritance, scope resolution, namespaces, constant expressions, concepts, coroutines, move semantics, ..., the list goes on. Not only that, but differences can be discerned even at the low level, when considering memory layout of C++ objects, and aliasing rules. So no, C++ is not just C with classes.

author: indeterminatus
author_rank: Contributor
timestamp: October 13, 2020, 07:43:08 am
content: The inverse also holds: you can do classes in C.It's just so much nicer if these are first-class citizens in the syntax of the language.

author: indeterminatus
author_rank: Contributor
timestamp: October 13, 2020, 07:56:18 am
content: Quote from: Syntax Error on October 12, 2020, 08:50:15 amSame thing, the result is undefinedin C and C++. The order in which the operands are evaluated for the right-hand side of the expression is undefined, which means that either the left-most "--i" or the right-most "--i" might be evaluated first. Since these things have the side-effect of modifying "i", the compiler might generate code (for initial i=10) that may be ( 9 - 8 ) or ( 8 - 9 ), or maybe even ( 8 - 8 ). I'm not so sure about the last one, though. It gets even worse when you look at whole expression.To summarize, don't use expressions with side-effects, please. Even if the language standard dictates an explicit order of evaluation (like Java does), it gets tedious to reason about it pretty quickly. While these are fun thought experiments, I'd rather not have constructs like that in a production-grade code-base.[edit: proper boundary conditions added in bold; also, made the parentheses spaced so they would not be converted to emojis]