2. Week 1 Tuesday: Errors
≪ 1. Week 0 Thursday: Getting Started | Table of Contents | 3. Week 1 Thursday: Integer Operations ≫Behind every computer program is the following pipeline of events:
- The engineering team gets together and designs the software architecture.
- The engineering team sits down and writes some code.
- The compiler and linker convert the code above into a computer program.
- The user runs the resulting program and has a pleasant experience.
At every step of the way, however, things can absolutely go wrong, and these are what we call “bugs” or “errors”.
By far the most common type of error to encounter at this stage is when a mistake is made in step 2 (writing code), thereby preventing the compiler (and/or linker) from understanding how to produce a computer program. These are called “build errors” or sometimes “compile errors” (and “linker errors”). However, this is the easiest kind of error to fix because the entire pipeline grinds to a halt at step 3 and the compiler issues a very verbose and descriptive error message that identifies exactly what went wrong. Being able to read and interpret these error messages is therefore a critical skill in software development.
We should mention that other kinds of bugs and errors very much do occur. Errors in step 1 are termed “logical errors” — this happens when there is an oversight or poor design that prevents a program from functioning as intended. An example is if someone is coding the game of chess. If they don’t understand the rules of the game correctly, or if the algorithms they design are poorly conceived, one can imagine all sorts of ways for the game to play out in strange ways. Perhaps white can eat their own pieces, for instance. Errors like this do not prevent one from writing correct C++ code, nor do they prevent the code from compiling, nor do they even prevent the program from running; in this way, they are the most sinister.
Surprisingly, errors in step 4 can happen too, and these are called “runtime errors”. These are experienced as programs crashing, or in extreme scenarios a blue screen of death or red ring of death. This is when your compiler has no problem interpreting the code written in step 3, but the instructions they produce cannot be executed for one reason or another. An example is if a program attempts to delete a file it doesn’t have permission to overwrite.
These last two types of errors need to be handled differently than compilation errors, as they sometimes do not come with detailed error messages indicating which line of code caused them. Often, they are not even caused by any one single line of code anyways…
Compilation Error Messages
Let us begin with a rather tame example. Consider the following rudimentary program:
1#include <iostream>
2using namespace std;
3
4int main() {
5 i = 17 * 28;
6 cout << "17 times 28 equals " << i << endl;
7 return 0;
8}
My compiler issues the following error message:
main.cpp: In function ‘int main()’:
main.cpp:5:5: error: ‘i’ was not declared in this scope
5 | i = 17 * 28;
| ^
main.cpp
indicates the code file in which the error occured. On the second line, main:5:5: ...
, the 5
indicates the line number and the 5
indicates which character in that line the error occured in. (The line is reproduced as well.) Following this, the message ‘i’ was not declared in this scope
indicates that we have referenced a variable before declaring it. The fix, of course, is to replace this line with int i = 17 * 28;
, for intsance.
In your IDE, the precise format of the error message may be slightly different, but you will always see the exact location of the error and a wordy description of what the compiler thinks of your code.
In this example, the error message says exactly what you did wrong, but it can sometimes be a bit more cryptic. Consider the following code:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int i = 17 * 28;
6 cout << "17 times 28 equals " << i < endl;
7 return 0;
8}
Perhaps you can visually identify the error, but let’s see what our compiler has to say:
Error message (open at your own risk)
main.cpp: In function ‘int main()’:
main.cpp:6:40: error: no match for ‘operator<’ (operand types are ‘std::basic_ostream<char>’ and ‘<unresolved overloaded function type>’)
6 | cout << "17 times 28 equals " << i < endl;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
main.cpp:6:40: note: there are 14 candidates
In file included from /usr/include/c++/15.2.1/string:50,
from /usr/include/c++/15.2.1/bits/locale_classes.h:42,
from /usr/include/c++/15.2.1/bits/ios_base.h:43,
from /usr/include/c++/15.2.1/ios:46,
from /usr/include/c++/15.2.1/bits/ostream.h:43,
from /usr/include/c++/15.2.1/ostream:42,
from /usr/include/c++/15.2.1/iostream:43,
from main.cpp:1:
/usr/include/c++/15.2.1/bits/stl_iterator.h:450:5: note: candidate 1: ‘template<class _Iterator> constexpr bool std::operator<(const reverse_iterator<_Iterator>&, const reverse_iterator<_Iterator>&)’
450 | operator<(const reverse_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:450:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::reverse_iterator<_Iterator>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:495:5: note: candidate 2: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator<(const reverse_iterator<_Iterator>&, const reverse_iterator<_IteratorR>&)’
495 | operator<(const reverse_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:495:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::reverse_iterator<_Iterator>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1689:5: note: candidate 3: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator<(const move_iterator<_IteratorL>&, const move_iterator<_IteratorR>&)’
1689 | operator<(const move_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1689:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::move_iterator<_IteratorL>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1755:5: note: candidate 4: ‘template<class _Iterator> constexpr bool std::operator<(const move_iterator<_IteratorL>&, const move_iterator<_IteratorL>&)’
1755 | operator<(const move_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1755:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::move_iterator<_IteratorL>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/stl_algobase.h:64,
from /usr/include/c++/15.2.1/string:53:
/usr/include/c++/15.2.1/bits/stl_pair.h:1073:5: note: candidate 5: ‘template<class _T1, class _T2> constexpr bool std::operator<(const pair<_T1, _T2>&, const pair<_T1, _T2>&)’
1073 | operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_pair.h:1073:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::pair<_T1, _T2>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/basic_string.h:51,
from /usr/include/c++/15.2.1/string:56:
/usr/include/c++/15.2.1/string_view:677:5: note: candidate 6: ‘template<class _CharT, class _Traits> constexpr bool std::operator<(basic_string_view<_CharT, _Traits>, basic_string_view<_CharT, _Traits>)’
677 | operator< (basic_string_view<_CharT, _Traits> __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/string_view:677:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘std::basic_string_view<_CharT, _Traits>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/string_view:684:5: note: candidate 7: ‘template<class _CharT, class _Traits> constexpr bool std::operator<(basic_string_view<_CharT, _Traits>, __type_identity_t<basic_string_view<_CharT, _Traits> >)’
684 | operator< (basic_string_view<_CharT, _Traits> __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/string_view:684:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘std::basic_string_view<_CharT, _Traits>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/string_view:692:5: note: candidate 8: ‘template<class _CharT, class _Traits> constexpr bool std::operator<(__type_identity_t<basic_string_view<_CharT, _Traits> >, basic_string_view<_CharT, _Traits>)’
692 | operator< (__type_identity_t<basic_string_view<_CharT, _Traits>> __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/string_view:692:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: couldn’t deduce template parameter ‘_CharT’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4164:5: note: candidate 9: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const __cxx11::basic_string<_CharT, _Traits, _Alloc>&, const __cxx11::basic_string<_CharT, _Traits, _Alloc>&)’
4164 | operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4164:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4178:5: note: candidate 10: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const __cxx11::basic_string<_CharT, _Traits, _Alloc>&, const _CharT*)’
4178 | operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4178:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4191:5: note: candidate 11: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const _CharT*, const __cxx11::basic_string<_CharT, _Traits, _Alloc>&)’
4191 | operator<(const _CharT* __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4191:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: mismatched types ‘const _CharT*’ and ‘std::basic_ostream<char>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/memory_resource.h:49,
from /usr/include/c++/15.2.1/string:72:
/usr/include/c++/15.2.1/tuple:2624:5: note: candidate 12: ‘template<class ... _TElements, class ... _UElements> constexpr bool std::operator<(const tuple<_Elements ...>&, const tuple<_Elements ...>&)’
2624 | operator<(const tuple<_TElements...>& __t,
| ^~~~~~~~
/usr/include/c++/15.2.1/tuple:2624:5: note: template argument deduction/substitution failed:
main.cpp:6:42: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::tuple<_Elements ...>’
6 | cout << "17 times 28 equals " << i < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/ios_base.h:48:
/usr/include/c++/15.2.1/system_error:326:3: note: candidate 13: ‘bool std::operator<(const error_code&, const error_code&)’
326 | operator<(const error_code& __lhs, const error_code& __rhs) noexcept
| ^~~~~~~~
/usr/include/c++/15.2.1/system_error:326:31: note: no known conversion for argument 1 from ‘std::basic_ostream<char>’ to ‘const std::error_code&’
326 | operator<(const error_code& __lhs, const error_code& __rhs) noexcept
| ~~~~~~~~~~~~~~~~~~^~~~~
/usr/include/c++/15.2.1/system_error:509:3: note: candidate 14: ‘bool std::operator<(const error_condition&, const error_condition&)’
509 | operator<(const error_condition& __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/system_error:509:36: note: no known conversion for argument 1 from ‘std::basic_ostream<char>’ to ‘const std::error_condition&’
509 | operator<(const error_condition& __lhs,
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~
This error message spans over a hundred lines for me, though your IDE probably organises it into something more sensible. What is most important, first and foremost, is still the second line, where it states
main.cpp:6:40: error: no match for ‘operator<’...
This indicates that something has gone wrong on line 6 of main.cpp
, and on the 40th character, which is the <
in ... i < endl;
. I should have typed ... i << endl;
instead. This is hinted by the error message itself, though what it exactly means is somewhat cryptic.
Although this is a clearly identifiable error, such things are much harder to catch if you are working on large projects consisting of thousands (or millions) of lines of code across dozens or even hundreds of code files. The point is that much of the output of these error messages is specific but verbose, and sometimes an error can be diagnosed without using the bulk of the message.
Here’s another variant of the above program:
1#include <iostream>
2using namespace std;
3
4int maln() {
5 int i = 17 * 28;
6 cout << "17 times 28 equals " << i << endl;
7 return 0;
8}
This time, I get the following error:
/usr/bin/ld: /usr/lib/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../lib/
Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status
There is no file or line number that’s identified as the source of the error. Nonetheless, the message undefined reference to `main'
suggests that something is wrong with our “main
” function. A closer look indicates that there was a typo, and we had mistakenly defined a “maln
” function instead.
Remark 1.
This is in fact an example of a linker error rather than a compilation error. The compiler was able to interpret all of the code together, but the linker could not figure out where to begin the program.
Remark 2.
It is okay if you did not correctly interpret this error message on your first try. If you ever get stuck, you can always search the internet for your error message, and frequently you’ll find that other programmers have been stumped by the same cryptic message as you. Searching the whole error message above verbatim yields several forum posts with the same issue.
Let’s look at one last variant of this simple program:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int i = 17;
6 i *= 28 += 5;
7 cout << "17 times 28 plus 5 equals " << i << endl;
8 return 0;
9}
The intention of this code is to apply the operation *= 28
to i
, multiplying it by 28
, then to chain together the operation += 5
to i
, adding 5
. Attempting to compile this code yields the following error:
main.cpp: In function ‘int main()’:
main.cpp:6:16: error: lvalue required as left operand of assignment
6 | i *= 28 += 5;
| ^
This is a genuinely useless error message, at least for now. It states that there is something wrong with the character 5
, then says something weird about lvalue
and assignment
and left operand
. An internet search for this issue gives a plethora of answers that don’t address why our code specifically doesn’t work.
On the bright side, it says something is wrong with line 6, so that’s a start. Commenting out line 6 (two forward slashes!) does make the code compile, although the output is wrong:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int i = 17;
6 // i *= 28 += 5;
7 cout << "17 times 28 plus 5 equals " << i << endl;
8 return 0;
9}
An idea is to try doing these two operations, *= 28
and += 5
, one at a time. We can comment out only the += 5
, remembering to insert a semicolon:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int i = 17;
6 i *= 28; // += 5;
7 cout << "17 times 28 plus 5 equals " << i << endl;
8 return 0;
9}
This does compile, though of course the output is wrong. Likewise, using i += 5
only without the *= 28
yields code that compiles with no errors. Thus we have identified the issue: we cannot chain together the operators *=
and +=
(or the other operators of a similar type). The fix is to run the two commands separately:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int i = 17;
6 i *= 28;
7 i += 5;
8 cout << "17 times 28 plus 5 equals " << i << endl;
9 return 0;
10}
The point of this short demonstration is that one can comment out chunks of code to slowly narrow down the origin of compilation errors when the culprit is unclear! We may see similar debugging techniques in the future.
Remark 3.
What’s actually happening is that C++ is performing 28 += 5
before running i *= 28
. But 28 += 5
is interpreted as the command 28 = 28 + 5
, which doesn’t make sense. The term lvalue
(very, very loosely) refers to any expression that could reasonably belong on the left side of the =
operator, hence the error message. For the record, using parentheses as in (i *= 28) += 5
can also resolve the error.
(This is not 100% accurate, you’ll have to take PIC 10B to learn more.)
Let us wrap up this discussion with some practise problems. Each of the following programs will produce a compilation error. Your job is to read and interpret the compiler’s error message, even if you can spot the error with your plain eyes! The point is to get practise understanding what the compiler is telling you for when you work on code that’s too large to read through manually.
These are arranged in no particular order; some may be easier or harder than others.
Problem 4.
1#include <isotream>
2using namespace std;
3
4int main() {
5 // converts the quantity "461 minutes" into a better
6 // format.
7 int minutes = 461;
8 cout << minutes << " minutes." << endl;
9
10 int hours = 7;
11 int minutes = 461 - 7 * 60;
12 cout << "That's also " << hours << " hours and "
13 << minutes << " minutes." << endl;
14 return 0;
15}
Problem 5.
Problem 6.
Problem 7.