...He lunged again, missed, hit the string, which wrapped itself insultingly around the sword-point. Maybe it was simply too difficult for him.
That thought didn't sit comfortably, so he came at the problem from a different angle. Obviously, he told himself, the reason I can't do it is because it's not difficult enough.
K.J. Parker - Devices and Desires
A compiling error is a flaw in a program's source code that prevents the compiler from being able to generate an executable program. The FreeBASIC compiler is just a computer program, and it can not guess about what we intend our code to mean. When it becomes confused, or is missing important information, the best it can do is let you know. It does so by reporting error messages.
For a programmer learning a new language, error messages can feel like they are the only thing standing between you and seeing your ideas come to life. A beginner will generate a lot of source code errors simply because he or she has misunderstood the rules of the language, but even the most experienced programmer sees his fair share of error messages. We all make typos and occasionally forget to take care of one or two details before we go to compile.
Source code errors, however, are the easiest type of mistakes to correct. After all, when a program won't compile, you know it is broken. What's more, the FreeBASIC compiler does everything it can to tell you how it is broken.
The trouble for the beginning programmer, however, is that the compiler does not have the picture that you as the programmer must have. You know how you intend each piece to fit together. The compiler has a totally different view point. Its nose is right up against each line, and it only sees one piece at a time. If a piece is missing, it has no idea what that piece should look like, so when it lets you know that your code is broken, it can be confusing at first to decipher the message it gives you.
With all that in mind, I don't want you to be disappointed when I tell you that this tutorial is not intended to teach you anything about programming in FreeBASIC. It is not even going to teach you how to fix mistakes in your programs.
Instead, the sole purpose of this tutorial is to help you become familiar with seeing compiler error messages and warnings. Instead of waiting for errors to pop up for the first time when you are trying to learn something else, we are going to force a handful of common error messages now, so that you will recognize them in the future.
To do that, we are going to take a functioning program... and break it.
If you are a complete novice, you're going to see a lot of words and terms that may be unfamiliar or used in a context you haven't seen before. Don't get too distracted by it. These are the main terms you need to understand.
To get the most out of this tutorial, you should actually run the FreeBASIC compiler with the example source code to see for yourself what the output of the error messages looks like on your system. As a programmer, you are going to see these messages a lot, but the more you see them, the easier it will be to understand them. You may as well get started now.
If you haven't already installed FreeBASIC on your system, and you are not sure how to do so, this tutorial might help: Installing FreeBASIC on Windows.
For the purposes on this tutorial, Windows Notepad will do. Just make certain that you save files in plain text format. It is also a good idea to turn of word wrap and enable the status bar.
If you have installed FBIDE, and would prefer to use it to edit and compile the examples, that will work as well. However, the instructions here assume only that you have access to a command console with the FreeBASIC compiler already in the default search path. If you have never run the compiler directly from a command console, you might benefit from this tutorial: A Short Introduction to FreeBASIC Command Line Compiling on Windows
Start a command console and your text editor.
If you use the Open Console program that was installed with FreeBASIC, then your current directory should already be set to your FreeBASIC directory, and this location should already be in your PATH environment variable.
If you have a wide enough screen, you may want to arrange your windows in a manner similar to this:
This will allow you to both read this page and follow along without having to move these three windows around too much. If this doesn't work for you, you can experiment with different layouts as you progress.
To keep your FreeBASIC installation some what clean, we will create a subdirectory under your FreeBASIC directory to store the files we will create. You can delete these files when you are done with them.
In the command console, use these two commands to create a subdirectory named cfTut and to enter that directory after it has been created.
Your command prompt should look something like this:
Here is the source code for the program that will be the test subject of our experiment.
It doesn't do anything special, and what it does do, it doesn't do that well. Right now, that doesn't concern us. For our purposes, it's just a lab rat.
You can Cut and Paste this program into your text editor, or you can download a copy here:Test.bas
Normally, I would recommend typing it manually, but right now it will be better to have an initial version that we already know has no source code errors.
Either way, save the source code as test.bas in the directory we just created.
'Preprocessor definitions #Define MAX_NUM 10 #Define MIN_NUM 0 #Macro MAKE_RANGE( _lb_,_ub_ ) ( "[" & ( _lb_ ) & " to " & ( _ub_ ) & "]" ) #Endmacro #Define RANGE_TXT MAKE_RANGE( MIN_NUM, MAX_NUM ) 'User data types Enum Range_Condition TOO_LOW IN_RANGE TOO_HIGH End enum 'Procedure declarations Declare Function InRange(byval value as Integer,_ byval min as Integer,_ byval max as Integer )_ as Range_Condition 'Program STARTS Here 'Display Description Print "This program will calculate the factorial" Print "of the number you supply." Print ' Blank line Print "Input may be in the range: " & RANGE_TXT Print ' Blank Line 'Get user input Dim as Integer userInput Dim as Range_Condition check Do Input "Enter a number:", userInput 'Validate Input check = InRange(userInput, MIN_NUM, MAX_NUM ) Select Case check Case TOO_LOW Print "Input is too low." Case TOO_HIGH Print "Input is too high." End select Loop until check = IN_RANGE 'Perform Calculation Dim as Double factorial = 1 For term as Integer = 1 to userInput factorial *= term Next term 'Display Results Print ' Blank line Print userInput & "! = " & factorial Print ' Blank line 'Program ENDS Here Function InRange(byval value as Integer,_ byval min as Integer,_ byval max as Integer )_ as Range_Condition If value < min Then function = TOO_LOW Elseif value > max Then function = TOO_HIGH Else function = IN_RANGE End if End function
Before we start to demolish this program, let's make sure it is already in working condition. I've already compiled this source about a dozen times... but you shouldn't take my word for it.
In the command console, compile the program with this command:
fbc -exx test.bas
If everything is as it should be, we should receive no output to the console from the compiler.
-exx compiler option in the command we just used enables
run time error checking. Even if a program compiles correctly,
this doesn't mean that the program is error free. The compiler
creates an executable that does exactly what we described in the
source code. Sometimes, we accidentally describe actions that
would cause our program to crash. There's little harm in this,
but its obviously not what we intended to do. Run time error
checking helps us identify and weed out this kind of mistake.
It won't catch every problem, but its better than nothing.
The compiler must add some extra code to our program to do
this, but once we are confident that our program is in full
health, we can recompile without the extra code.
Now, just to get an idea of what we are working with, go ahead and run the program with this command:
I know. It's not the latest killer app, is it? It does have its virtues though. It was the only program I could find that would volunteer for the torture we are going to inflict upon it.
We are going to make many very small changes to the source code of this program to see how the compiler responds when we give it bad code. We'll make each change one at a time, and restore the program to its original state before the next change. This way we can be sure that the error messages that we get are the result of the specific changes that we make.
The changes that we will make are going to be so minor that it will be really easy to undo them, but just in case things go off track, let's make a back up copy with this command:
copy test.bas test.saved
Now, if for some reason we find that we can't undo our damage we can simply restore the original code with:
copy test.saved test.bas
We'll also have to remember to reload test.bas in our text editor to make sure we are working with the restored copy.
Our first change will be to delete the very first character on the very first line of the source code. That line looks like this:
In your text editor, delete that
' symbol in front of the
word Preprocessor. Then save the file, and compile the program
again. (Remember fbc -exx test.bas )
Here's what we get:
test.bas(1) error 41: Variable not declared, Preprocessor in 'Preprocessor definitions'
This is a typical compiler message. It has four parts
test.bason line #1.
Variable not declared
Preprocessor in 'Preprocessor definitions'
test.bas. We knew that.
' symbol that we deleted is the FreeBASIC comment
marker. (One of them. There are a couple others). Basically,
the purpose of the comment marker ' is to instruct the compiler
to ignore everything that follows it on that line. This makes
it possible to include information directly in the source code
that is not intended to be used as code. In this case,
"Preprocessor definitions" was just a little remark about the
next few lines of code.
So the problem is that our comment is incorrect, but the compiler complained about an undefined variable.
That's wrong, isn't it? No!
The FreeBASIC compiler implements the FreeBASIC language. It defines the language. If it says "Preprocessor" is a variable, it is because we told it that is was. If we want "Preprocessor" to be a part of a comment, we have to say so.
It is as if a friend asked for directions, and we said "Turn left," when we meant to say "Go straight". Left means left, and now our friend is lost.
We, fortunately, do know that we meant that first line to be a comment, so we'll just put the ' symbol back. (It's on the same key as the double quote, right beside the enter key. ) Insert the comment marker before "Preprocessor". Save the file, and if we try to compile again, all should go smoothly.
Line #2 looks like this:
#Define MAX_NUM 10
# symbol, save, and compile. Now we get:
test.bas(2) error 41: Variable not declared, Define in 'Define MAX_NUM 10'
test.bas(28) error 41: Variable not declared, MAX_NUM in 'Print "Input may be in the range: " & RANGE_TXT'
That's two errors, both of the same type we saw a moment ago. Notice that the first problem is on the line we changed, but the second problem is down on line #28.
But... we didn't touch line #28.
If we look closer at the messages, we see that the symbol
MAX_NUM is causing trouble in line #28, and
MAX_NUM is in the line that we just changed.
As it turns out, these lines that begin with the
# symbol are preprocessor directives.
#Define was intended to set up a text replacement
macro. Removing that
# botched it up, so now the compiler
doesn't know what to do with the symbol
MAX_NUM when it shows
Now, when the compiler found the error on line #2, we know
the program won't compile.
Fbc.exe could just quit there,
but it doesn't. It forges ahead to look for more errors so that
it can give us as much information as possible. It'll keep
on reporting trouble as it sees it. Of course, if it finds
enough errors, it assumes that we have enough on our hands to
deal with and calls it a day.
Go ahead and put the
# symbol back in place.
Now, look at line #3:
#Define MIN_NUM 0
Change it so that the symbol
#Define MN_NUM 0
This line is syntactically correct. The compiler won't choke on it like it did with the first two changes we made. Try to compile.
test.bas(28) error 41: Variable not declared, MIN_NUM in 'Print "Input may be in the range: " & RANGE_TXT'
We screwed up line #28 again, but we didn't get an error for the
line we changed. It turns out that the change we made resulted
in code that was well formed. It created a preprocessor symbol
MN_NUM, and happily moved on. But again, we goofed line
#28 and... wait a minute!
These last two changes dorked the symbols
MIN_NUM, and both caused a problem in line # 28.
But line #28 looks like this:
Print "Input may be in the range: " & RANGE_TXT
MIN_NUM aren't even in that line!
In truth, I introduced all these preprocessor directives at the top of this program so that I could really misdirect the compiler with small changes.
Not only did I tell my friend to make a left, I sent him into downtown Pittsburgh where everyone will tell him, "You can't get there from here."
What these #Defines and #Macros are doing is instructing a part of the FreeBASIC compiler called the Preprocessor to actually rewrite the source code just before the compiler determines what it is supposed to mean.
This is a pretty handy tool to have, but as you can see, when it goes wrong, it can be somewhat difficult to track down the real problem.
In this case, the problem comes from the symbol
which depends on all five lines that precede it. A mistake
in any of these lines changes the intended meaning of
RANGE_TXT, which is in line #28.
Some programmers love the preprocessor. Others never touch it. Most fall in between. It can be used to accomplish huge effects with relatively little effort, but it can also introduce some of the most tangled source code errors.
Without fixing the error we just introduced into
use this command:
fbc -pp test.bas
We're going to the get the same error message, but in
addition, we also get a look at what the compiler sees
after preprocessing is done. With the -pp option in the command
line, the compiler doesn't try to compile the source code.
Instead, it preprocesses and checks syntax only, and it saves
the results of all those
symbols in a file ending in
In this case, the file will be
test.pp.bas in a text editor, and take a look.
The first thing you'll notice is that all the comments are gone.
All the lines starting with
# are gone too. All the lines
that were indented in
test.bas merely start with a blank space
This is pretty close to how the compiler sees the source code. Almost everything that it doesn't need is gone. (It doesn't need those blank spaces and empty lines, but it helps us a little. )
If you scan down, you'll find what has become of line #28.
Print "Input may be in the range: " & ( "[" & ( MIN_NUM ) & " to " & ( 10 ) & "]" )
This is exactly the code that I intended. The only problem
is that the compiler doesn't know what to do with
thanks to our meddling.
Don't worry if the code makes no sense to you right now. The only goal is to understand how mistakes in one place can show up as source code errors in other places.
If you are getting the general idea of what happened when we changed line #3, then it should be smooth sailing, because that's as complicated as it gets. The knot could have more loops and tie together more pieces, but it would just be more of the same problem.
test.bas back in working condition by changing
MN_NUM back to
MIN_NUM on line #3 so that we can move on.
So far we've introduced source code errors that could easily crop up due to simple typing mistakes. It's not hard to forget a comment marker or leave a letter out of an identifier. Most programmers don't write source code from top to bottom. Often the relationship between subsequent parts is not appreciated until the broad outline is sketched. It's usually a back and forth, in and out activity, which creates a lot of chances to make simple errors.
Some mistakes, however, are accidentally intentional.
On line #10, which is,
Lines #10 to #14 set up a custom data type called an Enumerated
Data Type. Don't worry about what that means. The idea here is
to pretend that we really did think that
Enum was supposed to
Enumerate. We haven't read the manual in some time, and we
haven't used an Enumerated Data Type for a while, so we just
forgot the right keyword. That's okay. We're learning.
(You do have the manual, right? )
So we compile and we get:
test.bas(10) error 41: Variable not declared, Enumerate in 'Enumerate Range_Condition'
test.bas(11) error 41: Variable not declared, TOO_LOW in 'TOO_LOW'
test.bas(12) error 41: Variable not declared, IN_RANGE in 'IN_RANGE'
test.bas(13) error 41: Variable not declared, TOO_HIGH in 'TOO_HIGH'
test.bas(14) error 34: Illegal 'END', found 'End' in 'End enum'
test.bas(20) error 14: Expected identifier, found 'Range_Condition' in 'as Range_Condition'
test.bas(33) error 14: Expected identifier, found 'Range_Condition' in 'Dim as Range_Condition check'
test.bas(39) error 41: Variable not declared, check in 'check = InRange(userInput, MIN_NUM, MAX_NUM )'
test.bas(66) error 14: Expected identifier, found 'Range_Condition' in 'as Range_Condition'
test.bas is done for. We might as well walk away. If we
had so much trouble tracking down that problem with line #28,
then we really have our work cut out for us now.
On the other hand, we did invest a bit of time writing this program, and we really want to see it run, so we roll up our sleeves and begin at the beginning.
The first error is on line #10. It looks right to us,
but the compiler is saying that
Enumerate looks like a
variable name, and not the keyword that we think it
should be...and remember, the compiler is always right.
We downloaded the help file when we downloaded FreeBASIC, so
we pull it up and look in the index for
We do see
Enum, though, and realize immediately what is causing
the error on line #10.
Let's correct it now, and change
Enumerate back to
One down. Eight to go.
Now, while the help file was open, we took the time to reread the description of Enum just to make sure we really understand what it does. When we move on to look at lines #11 through #14, we're at a loss. Everything looks like it does in the manual.
Before we begin to suspect that there's a bug in the compiler, it might be wise to save the correction we made to line #10, and then recompile to see if at least that error goes away.
Just like that, all nine error messages vanish. Looks like all those other source code lines were depending on line #10 to do its job. When it called out sick, they all went home too.
Here's the moral: Don't be fooled by a long list of error messages.
Start with the first problem the compiler found, figure it out, fix it, and compile again. Any error that was directly related to it will be solved at the same time. If you get new errors, then these were problems that the previous mistakes were preventing the compiler from spotting. Keep solving them one at a time. It's not unusual for two or three typos to result in a dozen or so error messages. No matter how many errors pop up, don't get discouraged.
As you develop, the source code mistakes that you make will become more and more trivial, and you'll find that it takes less and less time to correct them. You'll learn to program defensively, and you'll start tackling the real challenge: logical errors.
Not all messages that the FreeBASIC compiler reports are errors. An error condition prevents the compiler from producing an executable program. There are other conditions where the compiler is designed to alert you that although what it sees poses no problem for the compiler, you might not have intended the results. These are warnings.
Warnings are worse than errors. When you have a source code error, you either fix it, or you don't get a program. When the compiler gives you a warning, you still get an executable program, and often it does exactly what you meant it to do. So you get into the habit of simply ignoring warnings. That's when trouble strikes.
One type of warning will illustrate the point.
If you haven't put
test.bas back into service ready status,
do so now. You still have the backup if you need it.
Make sure the code will compile, and run the program again to refresh your memory concerning what it looked like.
This program will calculate the factorial of the number you supply. Input may be in the range: [0 to 10] Enter a number:5 5! = 120
When you are done, look at line #25
Print "This program will calculate the factorial"
The purpose of this statement is to output the string of textual data between the two double quotation marks. The first quotation mark is the opening quote and the second is the closing quote.
Every opening quote must have a closing quote... almost.
Remove the double quotation mark from the end of the line to make it look like this:
Print "This program will calculate the factorial
Save and compile.
Here's our warning:
test.bas(25) warning 13(0): Missing closing quote in literal string
The program will still successfully compile, and the program will behave exactly the same way as it did before.
This worked because in this case, the compiler is not expecting anything to come after the string, so it just treats everything after the opening quote until the end of the line as part of the string. Then it warns you that something looked fishy.
Some might say, "Hey, I don't need that closing quote."
Now, remove the closing quote from line #28. This time it is not at the end of the line. It is before the & symbol. Just delete that quote, save again, and recompile.
test.bas(25) warning 13(0): Missing closing quote in literal string
test.bas(28) warning 13(0): Missing closing quote in literal string
Now we get two warnings about missing closing quotes. Well, the warning for line #25 wasn't an issue, and we're sick of dealing with line #28. Things are probably just fine.
Now run the program and see if you notice what happened.
This program will calculate the factorial of the number you supply. Input may be in the range: & RANGE_TXT Enter a number:5 5! = 120
That's an error in the program. That & RANGE_TXT was not intended for output. We were warned.
"Big deal!" we can here some cry. "You spotted it the first time you ran the program. It's easy to fix."
We respond, "What if that string had been destined for something other than the console window? What if it had been stored in a file, not to be accessed for quite a while? What if the information that was supposed be output was important to someone?"
The point is that the compiler warns you when you do something risky. For every warning condition, there is always a way to code your intentions in such manner that tells the compiler, "Yes. That is what I wanted." By forcing yourself to rewrite code that causes warnings, you are that much more certain that your program should work properly.
The FreeBASIC compiler actually relies on a few external programs to get its job done. It handles the preprocessing and the interpretation of your source code, then it hands the rest of the process off to other programs to do the grunt work. On occasion, a problem in our source code doesn't stop the compiler, but stalls one of the helper programs.
Find line #63.
Function InRange(byval value as Integer,_
IsInRange and recompile.
We get this:
test.o:fake:(.text+0xeb): undefined reference to `INRANGE@12'
This doesn't look like any of the messages we've seen before.
We have to look pretty close before
INRANGE@12 begins to
resemble anything in our program.
This is a linker error. The linker is the program that takes bits and pieces of compiled code and finally stitches them together into an executable program.
What has gone wrong here, it that on line #39 we use the
InRange, but this symbol hasn't been defined before
line #39. Normally, the compiler would give us another
undeclared variable message, but in this case we did declare
the variable. Way back at line #17 we told the compiler that
somewhere a function answering to the name of
InRange does in
fact exist. It could be in this source code file,
some other source code file that we are going to compile at
the same time, or some object code file that has already been
Before we made our change,
InRange was finally defined down at
the bottom of our source code, where the compiler eventually
found it. Now, though,
InRange is not defined, so the compiler
assumes that it must exist elsewhere. It is the linker's job to
find it. It can't because it doesn't exist. No other code is
coming to rescue
We can fix it by changing
IsInRange back to
, or we can change line #39 and line #17 to match the new name.
I am sure that by now you would rather not see another error message for a long, long time. I have gone to the trouble of writing this tutorial so that you can spend more time learning the FreeBASIC language, and less time wondering why something doesn't work when the real issue is as simple as a missing coma, or a misspelled keyword.
Additionally, if you followed along and actually made the changes
test.bas and ran the compiler, you've mastered the
edit-compile-correct-compile cycle of a single file program.
If you are not yet tired of kicking
test.bas around, here is
another programming error you can introduce that won't
give you an error message: On line #52, delete the
from before the
= symbol. If you save, compile, and run the
program; see if you spot the bug.
By the way, there's been a bug in this program the entire time, and it has nothing to do with incorrect source code, and no change to just one or two lines will fix it. After you fix the bug I asked you to place on line #52, run the program and give it as many different values for input as you can think up. You'll find that for a certain set of numbers, part of the output is contradictory. I won't tell you what it is, but I will say that it has nothing to do with entering letters instead of numbers.