A fellow codementor made a post titled Basic debugging. Dude doesn't even get into how to debug at all. He mentions two ways, but then starts going off on how to write simple code. That is not really debugging, in my opinion. Sure, you often have to write code in order to debug, but that is not exactly the same thing. The process of writing simple code differs from the process of debugging.
Dude gets into writing simple code, and really, his rant is not about debugging at all.
First of all, I personally feel that simple code should be the standard. One statement per line. Very rarely mix function calls into other statements or expressions. Keep it simple so that when errors occur, you can reduce the bug to a line number (usually).
I think the post kind of set me off a bit because I frequently go through debugging processes with students, and the process of rewriting and/or refactoring (please God tell me the difference) code is usually something done in the process of debugging, but is not the process itself. Often, you should want to rewrite your own or others' code in order to achieve clarity, especially if you do not quite understand how something is achieved. Mixing statements and calls into complicated one-line expressions is not going to achieve any performance benefit to how efficient or fast or small your code is.
I was thinking about my favorite bugs, which are the most annoying. Usually, you've written perfectly logical code everywhere else, but you forget a critical single-character of syntax and the compiler/interpreter begins shitting a brick. In high school (circa 2003-2004) I made a C++ compiler spew out over 100 errors due to omitting a single semicolon (which is where my most often spoken phrase to C/C++/Java/Javascript/PHP students comes from: "Don't forget your semicolon").
I wonder...can I get that error to occur again, intentionally? And, if so, I can certainly show you how you'd interpret the error messages that get generated by the compiler.
Let's go with a simple example program in C++. We declare a class called MyClass
and write a basic Hello World program.
Can you spot the syntax error?
1 #include <iostream>
2
3 using namespace std;
4
5 class MyClass {
6 public:
7 MyClass() {
8 };
9
10 int main() {
11 cout << "Hello, world!" << endl;
12 return 0;
13 }
If you said "The constructor declared on line 7 is missing a closing curly-brace", you're correct!
However, let's look at what the compiler clang++
says about this code:
clang++ main.cpp
main.cpp:13:2: error: expected '}'
}
^
main.cpp:5:15: note: to match this '{'
class MyClass {
^
main.cpp:13:2: error: expected ';' after class
}
^
;
2 errors generated.
The compiler cites the curly-brace at the end of the file, but recognizes that the class declarator is missing its closing curly-brace. It actually cites line 13 twice and claims there are two errors:
- The closing curly brace it thinks is missing for the class declarator
- The closing curly brace it matches for the class declarator is missing a semicolon
Both of these errors are generated due to the way the compiler parses our code. The errors themselves are not the logical, syntactical problem we are actually facing, and so you cannot fully trust error messages.
Let's look at the output from another compiler: g++
main.cpp:13:1: error: expected ‘}’ at end of input
}
^
main.cpp:13:1: error: expected unqualified-id at end of input
Two errors again, but this time the actual error messages are different. Clang actually recognized the class declarator closing curly-brace was missing. This is not to diss on g++ or anything, but this is one of the first times I've actually compared output from the two.
What happens if you just blindly add an additional curly-brace at the end of the program, thinking "Oh, I'm just missing a curly-brace at the end, let's add that":
g++ main.cpp
main.cpp:14:2: error: expected ';' after class
}
Ok, so, let's add that missing ;
now:
Undefined symbols for architecture x86_64:
"_main", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Now it can't even find my main program!
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() {
};
int main() {
cout << "Hello, world!" << endl;
return 0;
}
};
Now the compiler isn't even returning an error line number or anything, it's just saying it can't find the int main()
. This is an even worse position to be in than before, and all because you couldn't take the time to read your own code...
Tsk tsk tsk.
What are we going to do about this?
A lot of the techniques you can use to hone-in on this sort of problem can be used to make the process of finding the bug easier. Formatting your code properly so that curly-braces and indents are symmetrical and allow your eyes to just go down the code without any sort of God-awful formatting (I have a rant on this building up and will be written eventually) is good, but that itself only makes debugging easier and is not, itself, debugging.
If you can't even trust the compiler's error messages, what can you trust?
- You can trust when your compiler fails to compile code. Just because code compiles does not mean that it is safe to run.
- You can trust your program crashes. Often, segmentation faults are due to incorrect pointer usage and memory referencing. Ever try to access the memory at location 0? Maybe this will be another blog post as well.
- You can trust the output of your code when it does successfully compile to return exactly what you tell it, so if it returns something incorrect, it is because your code is logically/semantically wrong and you need to look at what you're telling the computer to do exactly. Often, what you think you're telling the computer to do isn't actually what you want to tell it. Computers do not have the ability to infer your meaning very easily (type-checking, anyone?) so you must be explicit with your instructions. Anything less is asking for ambiguity. If you're not getting your desired output, examine your preconceptions about what you've already written and compiled, and see what needs to be changed.
Compiler error messages are sometimes only hints about what is wrong with your code, and in C++ in particular, are often not helpful at all.
Make sure that your curly-braces line up using whatever style you like, either Stroustrup-style or K&R-style:
Stroustrup-style
int main() {
return 0;
}
K&R-style
int main()
{
return 0;
}
And, don't forget your semicolons!
Top comments (5)
I wouldn't go as far as to say not to trust compiler errors and warnings. Too many coders would take that advice at face value and do terrible things (and, in fact, they do...)
A better rule is only fix the first compiler error in your list, and then recompile. Yes, it takes more time, but since compilation is basically top-down, the first syntax error is going to introduce a lot of compiler misunderstandings.
The first error, as you literally just demonstrated in your example, is almost always trustworthy.
TL;DR: Don't trust C++ compilers.
Also, I'm curious to why this is tagged with
java
since there is no mention to it.I did not include a Java example, but the problem exists in Java as well as interpreters in general.
You may want to use linters more before you compile the code( assuming that they exist in C++)
I have experience with linters, and in certain contexts they have been useful. At one place I worked, they helped enforce consistency in our codebases. I haven't needed to use them a lot in my own life.