63

This following code (containing a vicious bug) compiles with GCC without any warning. But, of course, it doesn't work as expected by the developer (me).

#include <iostream>

struct A
{
    bool b;
    void set(bool b_) { this->b = b_; }
    bool get() const { return this-b; } // The bug is here: '-' instead of '->'
};

int main()
{
    A a;
    a.set(true);
    std::cout << a.get() << std::endl; // Print 1
    a.set(false);
    std::cout << a.get() << std::endl; // Print 1 too...
    return 0;
}

Which warning can I add for the compiler (GCC 4.8) to avoid this kind of typo?

Linked question: Is there any option to force (or warn) the access to member variables/functions with this->?

PJTraill
  • 1,353
  • 12
  • 30
Caduchon
  • 4,574
  • 4
  • 26
  • 67
  • What if some strange individual wants to minus the `this` pointer by a boolean value? He should get a warning? – Griffin Sep 20 '17 at 10:17
  • Maybe you can't. In some weird cases your code makes some sense – Basile Starynkevitch Sep 20 '17 at 10:18
  • 2
    @Griffin If they've enabled the hypothetical warning option that OP asks for, then yes. – eerorika Sep 20 '17 at 10:18
  • 4
    This is correct C++ sentence, so no compiler will produce a warning. You should use static code analysis tool instead. – Ari0nhh Sep 20 '17 at 10:18
  • 2
    @Griffin you don't think they should? They could always turn off the warning in that part if they know that's really what they want to do – UKMonkey Sep 20 '17 at 10:18
  • I could have imagined `-Wconversion` (implicit conversion warnings), as a pointer is implicitly converted to bool - but that didn't get it... – Aconcagua Sep 20 '17 at 10:19
  • 18
    @Ari0nhh most compilers produce warnings for "correct" C++ sentences. At least when using explicit warning options. – eerorika Sep 20 '17 at 10:19
  • I'm pretty sure there's no warning for this, every single operation here is fairly sensible in some context. – Baum mit Augen Sep 20 '17 at 10:19
  • @Aconcagua That's use all the time in real world code, like `if(p && p->bla)`, you wouldn't warn for that. – Baum mit Augen Sep 20 '17 at 10:20
  • 7
    I think this demonstrates perfectly why unit testing has value. – UKMonkey Sep 20 '17 at 10:21
  • @BaummitAugen Not so common for return values... – Aconcagua Sep 20 '17 at 10:22
  • 2
    @BaummitAugen How about warning for doing pointer arithmetic on `this`? I wouldn't mind having such warning option although perhaps not included in `-Wall`. – eerorika Sep 20 '17 at 10:23
  • @user2079303 That would make sense in my eyes. Apparently, no one saw that as big enough of an issue so far, but I would not mind that warning either. – Baum mit Augen Sep 20 '17 at 10:25
  • You might want to try `-Waddress` (which is enabled by `-Wall`) and warns about suspicious uses of memory addresses. However, I'm doubtful - conversion of bool to `int` is well defined, pointer arithmetic (adding/subtracting an integer to/from a pointer), and conversion of pointer to `bool` are all well-specified conversions that are frequently used in practice. If such a construct as yours gave warnings, the false positives would be huge, and the warning would rarely be enabled. – Peter Sep 20 '17 at 10:25
  • 43
    don't use `this->`. That'll avoid the problem completely – phuclv Sep 20 '17 at 10:26
  • @Ari0nhh: `double x,y; x == y;` is also valid, but there is an obvious warning for that case. – Caduchon Sep 20 '17 at 10:38
  • 2
    @LưuVĩnhPhúc Not using `this` is sometimes not a very good option, like inheriting templates – Passer By Sep 20 '17 at 10:46
  • Interesting, `-Wpointer-arith` doesn't catch it. – BЈовић Sep 20 '17 at 11:27
  • 1
    Two more _not-real-solutions_: if you use a tool like Visual Assist, the mistake is far harder to make because it can automatically add `->` after a pointer. You could also use your version control pre commit hooks to stop instances of `this-` without a `>` following it. – Tas Sep 20 '17 at 12:47
  • 16
    "most compilers produce warnings for "correct" C++ sentences": I'd go as far as to say that *all* warnings are from "correct" C++ sentences. If it were not valid code, you would be getting an error instead. – Sean Burton Sep 20 '17 at 13:21
  • @BaummitAugen Pointer arithmetic on `this` can be meaningful, but converting the result to `bool` is not. Clang's `-Wundefined-bool-conversion` warns on implicit conversion of `this` to `bool`, which is always true. It could conceivably be extended to also warn on implicit conversion of `this-n` to `bool`, which is also always true. – Oktalist Sep 20 '17 at 13:23
  • @BasileStarynkevitch assuming the object is part of an array and accessing the previous one -- then getting fired :) – Quentin Sep 20 '17 at 15:15
  • 1
    Ugh, it should be just illegal to have a binary subtraction without whitespace around it in the first place. @LưuVĩnhPhúc: I know that's common but I consider it bad practice. It also forces you to put the base class name when inheriting (if you have dependent types involved), which is annoying and inconsistent. – user541686 Sep 20 '17 at 21:51
  • 2
    @LưuVĩnhPhúc You are right, `this->` should be avoided where possible. However, there are cases where `this->` is required (template inheritance), so it should be noted that the places where `this->` is indeed required are also the cases where an erroneous `this-b` is an error (because `b` is inaccessible without the `this->`). So the rule is: Don't use `this->` *unless required*. – cmaster - reinstate monica Sep 21 '17 at 08:13
  • 3
    I hope the people who conspired to make subtracting a boolean from a pointer legal in C++ feel at least a *little* guilty. The pointer-to-bool conversion is just about defensible, but allowing subtraction with bools? – Jeroen Mostert Sep 21 '17 at 09:27

4 Answers4

71

This particular issue is detected by cppcheck:

$ cppcheck --enable=all this-minus-bool.cxx 
Checking this-minus-bool.cxx...
[this-minus-bool.cxx:7]: (warning) Suspicious pointer subtraction. Did you intend to write '->'?
(information) Cppcheck cannot find all the include files (use --check-config for details)

This was with no include path given. If I add -I /usr/include/c++/4.8/, the issue is still detected:

Checking this-minus-bool.cxx...
[this-minus-bool.cxx]: (information) Too many #ifdef configurations - cppcheck only checks 12 of 45 configurations. Use --force to check all configurations.
[this-minus-bool.cxx:7]: (warning) Suspicious pointer subtraction. Did you intend to write '->'?
[/usr/include/c++/4.8/bits/ostream.tcc:335]: (style) Struct '__ptr_guard' has a constructor with 1 argument that is not explicit.
[/usr/include/c++/4.8/bits/locale_classes.tcc:248]: (error) Deallocating a deallocated pointer: __c

and then cppcheck slowly works through the aforementioned #ifdef configurations.

(As a side note, the error in local_classes.tcc is a false positive but this is very hard to tell for an automated tool, as it would need to be aware that the catch block at this site should not be entered when the macro __EXCEPTIONS is unset.)

Disclaimer: I have no other experience with cppcheck.

Arne Vogel
  • 6,346
  • 2
  • 18
  • 31
  • 11
    I prefer false positive than insidious bugs. :-) – Caduchon Sep 20 '17 at 11:27
  • Of course… the tool doesn't seem to generate many in any case, and it also allows writing suppressions (haven't tried this yet). – Arne Vogel Sep 20 '17 at 11:40
  • 9
    @Caduchon That's true up until the point where you get so many false positives that you can no longer see the important warnings. – JPhi1618 Sep 20 '17 at 14:33
  • 2
    @JPhi1618: that's also true. I have the problem with Intel compiler. I can't disable warning on boost. I get more than 100.000 warning in boost libraries. Impossible to see mine. – Caduchon Sep 20 '17 at 15:18
  • 3
    I am a Cppcheck developer. I would really like that it would be a defacto tool used by C/C++ developers. And then this answer would be good enough for op. I wonder if anybody has tried Cppcheck and decided it wasn't good for some reason? I would be happy to learn why. We really try to avoid noise in Cppcheck... I quote @ArneVogel "the tool doesn't seem to generate many in any case". We have suppressions but if you see wrong warnings it is recommended that you report it so we can fix it. – Daniel Marjamäki Sep 22 '17 at 10:47
32

No, this - b is performing pointer arithmetic on the pointer this, despite b being a bool type (b is implicitly converted to int).

(Interestingly, you can always set this + b to a pointer where b is a bool type, since you can set a pointer to one past the end of a scalar! So even your favourite undefined behaviour spotter would permit that one.)

Array bounds checking has always been the job of a C++ programmer.

Note also that in your cases the use of this is superfluous: so curtailing this excessive use is one way of making the problem go away.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Actually, we decided from a long time to systematically use `this->` for understandability reasons when reading the code. Maybe a bad idea. But changing that is really costly now. – Caduchon Sep 20 '17 at 10:44
  • 10
    @Caduchon: That decision was completely yucky. Where do you draw the line? Calling private member functions with `this`?! Spend the money and fix. Mind you, I use `m_` for member variables, and `s_` for statics. Back in my box ;-) – Bathsheba Sep 20 '17 at 10:46
  • Intel's pointer checking compiler should catch this - icpp Option Qcheck-pointers. At least if, as in question, the object this points to us not allocated as an array. If allocated as an array, icpp may not check this, since, as mentioned in answer, it is legal. But IMHO object methods should not care how object is allocated, so if I were still at Intel I would want to catch this. There was been talk of adding such checks to gcc or LLVM. – Krazy Glew Sep 20 '17 at 10:46
  • Actually, pointer checker would not error the code in the example, since it only prints the pointer. It would only catch if you tried to dereference. – Krazy Glew Sep 20 '17 at 10:48
  • 9
    @Bathsheba: using `this->` offer the guarantee at compilation time that it's a member. Using `m_` or `s_` doesn't prevent against a local/global variable (unfortunately) named like that. It was the reason. But, of course, it could be interesting to reopen this decision. StackOverflow is not the best platform to discuss that kind of question (I think). Which could be a good one ? – Caduchon Sep 20 '17 at 10:55
  • 2
    I suspect that a sufficiently clever compiler could emit a warning, relying on the fact that `bool(this-n)` is always true. For example, clang already emits a warning for implicit `bool(this)`. – Oktalist Sep 20 '17 at 13:15
  • 10
    I don't see how any part of this answer is relevant to the question about warnings. Of course it's pointer arithmetic. That doesn't mean that there's no way to detect that it's fishy or generate a warning. – user2357112 Sep 20 '17 at 17:23
  • Well, I rather like it: but that is what the voting mechanism is for. Please downvote; it helps users of this site gauge the usefulness of answers. – Bathsheba Sep 20 '17 at 18:02
  • @Bathsheba I've always used m_ for members, but I never thought to use a different one for static variables. Is this something you've seen elsewhere? It seems like a good idea. – Krupip Sep 20 '17 at 19:45
  • 1
    @Oktalist - but it *isn't* always true: if `b` is true and `this == (A *)(sizeof A)` (which is a legal albeit unlikely location for an A to exist in, at least on platforms where the size of a pointer is the same as the size of `size_t`, which is true in most cases) it will return false. – Jules Sep 21 '17 at 01:54
  • @Jules: If the compiler is targeting Linux, for example, it could assume that `/proc/sys/vm/mmap_min_addr` has its default value of `65536`, and thus no valid address inside the program can be within the low 64kiB. Or at least warn based on this, if not actually optimize away the math. (Fun fact: one use-case for changing that kernel setting is with emulators/wrappers (WINE for 16-bit binaries, QEMU, DOSBOX) using virtual address space as guest physical addresses: https://wiki.debian.org/mmap_min_addr. Apparently modern QEMU and DOSBOX work around it now.) – Peter Cordes Sep 21 '17 at 02:58
  • 2
    @snb See https://stackoverflow.com/questions/1228161/why-use-prefixes-on-member-variables-in-c-classes. I *think* I got my `m_` and `s_` when I was a wee nipper at Goldman Sachs. I've also seen `my` as a prefix for members, and `our` as a prefix for statics. Personally though I don't like to rely on syntax colouring to denote member variables: It's rather hard to configure vi to do that! – Bathsheba Sep 21 '17 at 06:55
  • 2
    @Jules No, [conv.ptr] says the only way to make a null pointer is `nullptr` or an integer literal zero, and [conv.bool] says all other pointers are `true`. The only way to contrive a situation in which `this-n` is `false` is by having undefined behaviour. – Oktalist Sep 21 '17 at 12:56
13

I would like to suggest another tool (apart from cppcheck proposed by @arne-vogel), giving a better visual aid instead of the warning asked for:

Use clang-format to automatically format your code. The result might look like this (depending on the settings), making the bug more visible by the spaces added around operator-:

struct A {
  bool b;
  void set(bool b_) { this->b = b_; }
  bool get() const { return this - b; }
};
Simon
  • 778
  • 6
  • 18
  • If you can also add some code coloring or compiler output coloring tool in your answer, you would increase its worth. – displayName Sep 20 '17 at 19:25
  • @displayName I am not sure I understand your suggestion, I am not aware of any editor or IDE used for software development that does not have syntax highlighting built in. – Simon Sep 20 '17 at 19:34
  • That makes me feel old because I know a few which did not have syntax highlighting at least. Nvm. I was only saying that if you are going to take help of code formatting to catch the issue (which is a good way, IMO), then use the formatting of compiler output etc as well to catch this as well as other potential errors. – displayName Sep 20 '17 at 19:39
  • Good idea. But actually, I hate when the editor formats my code. For many (stupid) reasons, I decide to format by a rule or another in the moment, for readability reasons, for aesthetic reason, for history reasons,... I prefer to keep control on that. By the way, I already have an editor doing auto-completing, precompiling, formatting, highlighting... It was a simple code, written very fast, it's only my mistake. – Caduchon Sep 20 '17 at 22:46
-1

No, there is no way to get a warning. The implicit conversions, although perverse, are allowed by the language.

However, in this specific use case we can do better - by wrapping the bool in a wrapper class which has explicit conversions and no arithmetic operations defined.

This results in a compiler error when logically misused, which is normally seen as preferable to a warning if logical correctness is the goal.

It is interesting to note that c++17 deprecates bool::operator++ as this arithmetic is seen as evil.

example:

struct Bool
{
    explicit Bool(bool b) : value_(b) {}
    explicit operator bool() const { return value_; }
private:
    bool value_;

    // define only the operators you actually want
    friend std::ostream& operator<<(std::ostream& os, const Bool& b) {
        return os << b;
    }
};

struct X
{
    bool foo() {
        // compilation failure - no arithemetic operators defined.
        // return bool(this-b);

        // explicit conversion is fine
        return bool(b);
    }

    Bool b { true }; // explicit initialisation fine
};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • @BЈовић yes I did, and provided a flawless solution that exceeds the OP's needs. – Richard Hodges Sep 20 '17 at 11:17
  • 2
    Then, I also have to recreate a `std::vector` reducing memory ? It's obviously not a solution. I don't want to change a basic type in all my code... – Caduchon Sep 20 '17 at 11:23
  • @Caduchon you don't have to, you can explicitly cast to bool. In any case, don't use std::vector - it's accepted as being a flawed concept in the standard library. – Richard Hodges Sep 20 '17 at 12:11
  • @RichardHodges maybe for my new code. But what about checking my current 2.500.000 lines of code ? Moreover, I use `std::vector`. The main unit using memory in the code is a class containing several very large vectors of boolean values. It easily saved a lot of memory, passing from 5Go to 1Go for a normal usage. – Caduchon Sep 20 '17 at 12:35
  • @Caduchon If this approach does not work for your specialised use case, that's fine. The OP wants some compile-time checking that he's writing correct logic. This solution does that. It makes no further claims. – Richard Hodges Sep 20 '17 at 12:42
  • 3
    I am not sure how replacing one problem with multiple other problems (replacing old usages of bool with your custom class or having inconsistent codebase, making sure any new developers also know about this and use it etc., and also having boilerplate code all over the place) is a good solution. Well, it does what OP wanted, but I seriously hope nobody actually does this. – Noctiphobia Sep 20 '17 at 12:55
  • @Caduchon I have offered a workaround for the question you posed. If your real problem is something else, please by all means post that and I'll see if I can provide a useful answer. Sorry I couldn't help this time. – Richard Hodges Sep 20 '17 at 13:05
  • 3
    @RichardHodges: I explicitly ask for a warning for the compiler. – Caduchon Sep 20 '17 at 13:13
  • 1
    There are several implicit conversions that generate warnings, e.g. `-Wundefined-bool-conversion` in clang. – Oktalist Sep 20 '17 at 13:32
  • 1
    @RichardHodges Well, nothing wrong with your suggestion but... is it not overkill? Why stop to `Bool`? Why won't we also define `Int`, `Float`, `Double`, `VolatileUnsignedLongLong`, ..? – YSC Sep 20 '17 at 14:42
  • 5
    "No, there is no way to get a warning. The implicit conversions, although perverse, are allowed by the language." That is a non-sequitur. Many (?all) warnings are for things that are allowed by the language, but are possibly a mistake. For example `if (a = f())` will produce a warning for many compilers (that will be silenced by `if ((a = f()))`. – Martin Bonner supports Monica Sep 20 '17 at 15:15
  • @YSC because bool is the only arithmetic type which arguably should not be arithmetic. – Richard Hodges Sep 20 '17 at 15:29
  • 1
    @RichardHodges - well, wrapping a built-in type does seem like utter overkill. And also, while it solves *this* particular version of the problem wouldn't solve *all possible ones*, e.g. if the field being accessed is a pointer and the method is returning a bool for whether it is null or not. Plus you claim that the fact that the conversion is allowed as part of the language means that you can't have a warning for it, which is not true, e.g. `g++` issues a warning for narrowing conversions in some circumstances where the language spec allows them. – Jules Sep 21 '17 at 02:07
  • 1
    @Caduchon and Richard: re: `std::vector`: A dynamically-sized bitmap is a useful data structure, but it's unfortunate that C++ chose to call it `std::vector` instead of `std::bit_vector` or something. *That's* something I think almost everyone can agree on. See https://isocpp.org/blog/2012/11/on-vectorbool for some excellent points, especially that the standard library should provide such a type, with optimized implementations for `std::find`, `std::count`, and so on, because that's a good way to expose platform-specific popcnt / bit-scan stuff. – Peter Cordes Sep 21 '17 at 03:15
  • 1
    And BTW, clang with [`libc++`](https://libcxx.llvm.org/) does have optimized implementations of some `std::` algorithms over `std::vector`, so rolling your own bit vector just to avoid `vector` will hurt performance on some C++ implementations. – Peter Cordes Sep 21 '17 at 03:17