2

In C or C++, modifying loop variables inside the for-loops is a source of nasty bugs:

int main() {
    std::vector<int> v (30);
    std::iota(v.begin(), v.end(), 0);
    int j = 0;
    for (size_t i = 0; i < v.size(); i++) {
        std::cout << v[i] << ' ' << i << '\n';
        i++; // oops, I mean j++
    }
    std::cout << j << '\n';
}

Is there any way to ban or get warning modifying loop variables inside loop bodies with aid of compilers or something? If it is possible, how can I do so?

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
frozenca
  • 839
  • 4
  • 14
  • 6
    I would suggest better naming. `i` and `j` are not very specific, that's why you end up modifying `i` instead of `j` in your example. – DimChtz Nov 05 '19 at 14:47
  • In some other languages this can be done by a special type of iterator that returns a tuple of the element and its index, but I would argue that doing it in C would be an anti-pattern. Perhaps doable in C++. – Arkku Nov 05 '19 at 14:48
  • 1
    @TedLyngmo: The question explicitly asks about C and C++. That the sample code is C++ does not negate that. Posters often tag both C and C++ when they are truly dealing with only one, but this question is not unreasonable to ask about both. – Eric Postpischil Nov 05 '19 at 14:57
  • re. iterator that returns both the element and its index, see: https://stackoverflow.com/questions/24881799/get-index-in-c11-foreach-loop (C++ specific) – Arkku Nov 05 '19 at 14:57
  • 1
    Might be helpful: https://stackoverflow.com/a/10962575/4342498 – NathanOliver Nov 05 '19 at 14:58
  • @EricPostpischil Ok, but wouldn't it be clearer to ask two separate questions? Or more if one wants to throw in Java too etc. – Ted Lyngmo Nov 05 '19 at 15:11
  • Disclaimer: I didn't downvote any answer so far. However, these solutions work but not seem neat.. – frozenca Nov 05 '19 at 15:12
  • @frozenca what you're looking for isn't really something that's supposed to be solved in a "neat" way. Can't really blame our answers :\ I'm sorry someone thinks those are not answering the question. – Marco Bonelli Nov 05 '19 at 15:14
  • Yes, what I'm looking for was just if there is a compiler option that warns it. If there isn't, maybe I should just code more carefully.. – frozenca Nov 05 '19 at 15:17
  • I see, I updated my answer. – Marco Bonelli Nov 05 '19 at 15:21
  • Being able to manipulate the loop variable is sometimes an important feature. For instance when you need to remove some entries from a set. The classical C approach would be to overwrite the removed entries with the last entry in the array, and decrement both the loop bound and the loop counter to reconsider the moved entry within the next iteration. – cmaster - reinstate monica Nov 05 '19 at 15:24
  • Typically this would be done with an external MISRA-C checker. It's a static analysis tool that checks for MISRA compliance, and MISRA bans you from modifying the iterator from within the for loop body, among other things. – Lundin Nov 05 '19 at 15:34
  • @Lundin, I was also wondering myself if a MISRA-like checker was not doing the trick. Thank you to confirm. Should definitely be THE answer. – Guillaume Petitjean Nov 06 '19 at 07:22

4 Answers4

3

If you use a C++ ranged-for, you can make the loop variable const. e.g.

for (const size_t i : boost::irange<size_t>(0, v.size()))
{
    std::cout << v[i] << ' ' << i << '\n';
    // i++; // error, can't modify const
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
2

For C++ you could create an index class to use. So something along the lines of the following would be a starting place. I'm sure that it can be improved as I haven't put much thought into it.

class CIndex {
private:
    size_t  m_index;
public:
    CIndex(size_t i=0) : m_index(i) {}
    ~CIndex() {};

    size_t inc(void) { return ++m_index; }
    size_t val(void) { return m_index; }

    bool operator < (size_t i) { return m_index < i; }
    CIndex & operator =(size_t i) = delete;
};

and it would be used something like:

for (CIndex x; x < 10; x.inc()) {
    std::cout << argv[x.val()];
    x = 3;    // generates an error with Visual Studio 2017
}

You could modify the above class with a conversion operator to make it a bit more intuitive and similar to a standard size_t variable. Also add a decrement operator as well. Since the idea is to use this in place of a size_t you no longer need the comparison operator since the compiler will do the conversion and use built in comparison for loop end. You may also want to be able to specify an optional increment or decrement amount.

The modified class would look like:

class CIndex {
private:
    size_t  m_index;
public:
    CIndex(size_t i = 0) : m_index(i) {}
    ~CIndex() {};

    size_t inc(size_t i = 1) { return (m_index += i); }    // increment operator
    size_t dec(size_t i = 1) { return (m_index -= i); }    // decrement operator

    CIndex & operator =(size_t i) = delete;
    operator size_t() const { return m_index; }
};

This would allow you to use the CIndex pretty much anywhere a size_t could be used. So an array index could be written as std::cout << argv[x]; rather than std::cout << argv[x.val()];.

However for C this is nothing within the language specification that allows you to mark a variable as immutable or unchanging within a particular scope.

What you are really asking for is to be able to mark particular lines of code to allow a variable to be changed and to mark other lines of code where the variable is not allowed to be changed. The C language specification does not have that feature.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
1

In C, you could hide the name and redeclare another identifier with the same name as const, but you will need to use some intermediate object to help, and it is not pretty:

for (int i = 0; i < 10; ++i)
{
    const int t = i, i = t;
    printf("i = %d.\n", i);  // Works.
    i = 4;                   // Yields compiler error.
}

I do not recommend this, but you can make it somewhat less ugly with:

#define Protect(Type, Original) \
    const Type Auxiliary_##Original = Original, Original = Auxiliary_##Original

and then use:

for (int i = 0; i < 10; ++i)
{
    Protect(int, i);
    printf("i = %d.\n", i);  // Works.
    i = 4;                   // Yields compiler error.
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 2
    You could do the same in C++, however this approach will also trigger nasty shadowing warnings. – user7860670 Nov 05 '19 at 14:54
  • Sorry, I might have misunderstood, but how does it answer to the question ? – Guillaume Petitjean Nov 05 '19 at 14:59
  • 1
    @GuillaumePetitjean: After the definition `const int i = t;`, it is impossible to modify the `i` defined in the `for` statement because it is not visible, thus satisfying the question’s request to “ban” modifying loop variables inside the body of the loop. Additionally, the loop variable `i` remains available for use without modification, as a copy is provided in the inner `i`, and attempting to modify it (by normal means; one could take its address and do bad things) will result in a compiler diagnostic. – Eric Postpischil Nov 05 '19 at 15:02
  • 2
    This doesn't really solve the problem of protecting the loop variable from being changed in the loop. The new variable `I` within the scope is not the loop variable and you are just introducing more complexity to attempt to work around a feature of C and C++. We all know that adding complexity and doing work arounds seldom results in good outcomes and is one of the most common sources of code rot. – Richard Chambers Nov 05 '19 at 15:13
  • 1
    @RichardChambers: How does it not protect the loop variable from being changed in the loop? In the loop, counting the set-up code shown in this answer as part of that, so the “inside” is the part from the declaration of the second `i` to the closing brace, it is impossible to change the loop variable. Thus it is protected. – Eric Postpischil Nov 05 '19 at 15:22
  • The loop variable is not the same variable as is in the scope. If you make a mistake and reference the loop variable outside of the scope then it will be modified. In your simple example of a small loop, errors of this kind are easy to identify. In large complex loops with multiple scopes from ifs and after a number of years of maintenance it's a source of defects. And it is an uncommon and unwieldy workaround that is guaranteed to be confusing to the next programmers who come along for maintenance. C just doesn't have this facility. – Richard Chambers Nov 05 '19 at 15:34
  • @RichardChambers: Outside of what scope? The idea is the code `for (stuff) { const int t = i; { const int i = t; … } }` would form the loop. You cannot refer to `i` outside of that scope because it is not visible. You cannot refer to the `i` of the `for` inside the `…` because it is not visible. If you mean somebody might put code inside the `} }` or before the `const int i = t;`, well, that is true with anything: If C had a construct called `For` or `ProtectedFor` that did what the OP requests, somebody could mistakenly use a `for` instead of a `ProtectedFor`… – Eric Postpischil Nov 05 '19 at 15:38
  • @RichardChambers: … There is no absolute protection from writing incorrect source code. In this case, one could bundle these into a macro that is not terribly bad. Actually, the extra braces are not needed; `for (stuff); { const int t = i; const int i = t;` suffices. The code shown (which I do not recommend; the answer is to provide facts and education, not to recommend practices) does in fact protect the loop variable from being modified. – Eric Postpischil Nov 05 '19 at 15:40
  • @EricPostpischil, I see what you mean. Even though it is pretty smart, I don't think (as you pointed out yourself) that it is a solution that can be used in a real program (but still interesting to know). – Guillaume Petitjean Nov 05 '19 at 15:44
0

EDIT: To answer your latest comment:

Yes, what I'm looking for was just if there is a compiler option that warns it. If there isn't, maybe I should just code more carefully.

No, in C there unfortunately isn't. Yes, you should code more carefully. In general, I would suggest you to think about why you would want to have such feature. If "protecting" index variables inside loops is a concern, I would first ask myself if my coding style makes sense and is consistent.


As Eric Postpischil noticed, you can just use a temporary variable to shadow your index variable inside an inner block as const. This way the compiler will error-out if you try to modify it.

This however produces shadowing warnings (in particular with the -Wshadow flag, which is pretty common):

shadow.c:9:14: warning: declaration of ‘i’ shadows a previous local [-Wshadow]
    const int i = t;
              ^
shadow.c:4:11: note: shadowed declaration is here
  for (int i = 0; i < 10; ++i)

To avoid this, you can use simple diagnostic #pragma directives and temporarily disable the warning for that specific code block:

for (int i = 0; i < 10; ++i)
{
    const int t = i;
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wshadow"
    {
        const int i = t;
        printf("i = %d\n", i);
        i = 4; // Will yield a compiler error.
    }
    #pragma GCC diagnostic pop
}

The above code works (removing the i = 4 of course) and compiles without warnings using -Wall -Werror -Wshadow -pedantic in both GCC and Clang.

NOTE: this surely is no good practice, but AFAICT it's the only way to achieve such behavior in C.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128