errno.h
tags: compiling, errno.h, linking, make
Since the Standard mentions the details of errno.h
first, 7.1.4 "Errors
<errno.h>", then we'll start there. This post will look into how to
implement a global variable, errno
, some macros, and how to properly link
object files together so that all of your symbols resolve correctly.
About errno.h
The standard states that errno.h
provides several macros relating to the
reporting of error conditions. Specifically, a "modifiable lvalue that has type
int
", and two macros: EDOM
and ERANGE
. So, what is a modifiable lvalue and
what are macros?
Lvalues
The standard defines lvalues in section 6.2.2.1, "Lvalues and function
designators". An lvalue is an expression that designates an object, with an
object type or incomplete type other than void
. The definition of a modifiable
lvalue is a little more complicated: an lvalue that doesn't have an array type,
an incomplete type, or a const-qualified type, and if it is a structure or
union, it has no members that are of const-qualified type. Basically, an object
which you can assign a value to. The term "lvalue" can be thought of as meaning
"left value". In expressions like object = value;
, the object
is a
modifiable lvalue; it's on the left and we are modifying it.
Macros
Macros are a bit simpler. Section 6.8.3 of the standard, "Macro replacement",
states that the identifier immediately following the define
is called the
macro name. For example:
#define MY_MACRO (10)
MY_MACRO
is the macro name. Immediately following a macro of this form is the
replacement-list followed by a new-line. This means that when the preprocessor
runs through our C code, every instance of MY_MACRO
will be replaced with the
characters (10)
. This makes our code much more readable and allows us to avoid
using "magic numbers" which are values which have no apparent meaning.
For example, if you are using strings within your program and you know that you only want to handle strings which are less than or equal to 30 characters in length, you could have something like:
if (30 >= strlen(myString))
{
/* process string */
}
This is fine since you wrote it, but there is a good chance someone else may look through your code and when they come across this 30 they would wonder what meaning it has. It might be easy to infer than it's the max string length, but they would have to further investigate your code to be sure. Instead, you could define a macro for the max string length and then use it like so:
#define MAX_STR_LEN (30)
...
if (MAX_STR_LEN >= strlen(myString))
{
/* process string */
}
Now the next person can easily determine that you were checking to be sure the string was within your limits. In addition, it's likely that you would use that value several times within your program and at some point you might decide that you will now handle strings up to 50 characters in length. Instead of having to update every instance of 30 that corresponds to your string length, you only need to modify your macro and everything is automatically updated for you! This will help you avoid bugs since every relevant instance of your max string length is updated, rather than you having to find and replace them manually. Let the preprocessor do the work for you.
errno
So, errno
is a modifiable lvalue of type int
which should mean we can just
define it as int errno;
, right? Well, the standard explicitly states that
errno
does not have to be the identifier of an object and that it could expand
to the result from a function call like *errno()
. The way I'm choosing to
implement it is with an object of type int, rather than with a function. The use
case for having a function macro is that every thread of your program would have
its own instance of errno
. This prevents threads from stepping on each other's
error conditions. However, multi-threading support is another beast so I'll just
stick to getting things working on one-thread before moving on to more.
The idea behind errno
is that the library will update errno
when somethings
goes wrong and return an indication to the caller that it should check errno
.
This means that our errno
object needs to have global scope such that any part
of the code can check it. The way we accomplish this with C is through the use
of the extern
storage-class specifier.
Declarations vs. definitions
We need to take a short detour on the difference between declarations and definitions. The standard provides some information about them in section 6.5, "Declaration". A declaration specifies the interpretation and attributes of a set of identifiers; i.e. the storage-class, type-specifier, type-qualifiers, and the name. A definition is what causes storage to be reserved for an object.
Some examples of declarations in C look like:
extern int a;
int
main(void);
Note that these are at file scope, and not within a function. This declares an
object, a
, with external linkage and of type int
, in addition to a function
of type int
, named main
which takes no parameters. This indicates to the
compiler that these two objects will be defined elsewhere and not to reserve any
storage for them since the storage will be reserved by other code.
Some examples of definitions in C look like:
int a;
int
main(void)
{
int b;
int c = 0;
return c;
}
Each object here is defined and has space reserved for it: a
, main
, b
, and
c
. The two questionable objects are a
and b
; we never assigned any value
to them yet they are still definitions? With C, declarations at file scope,
without external linkage (i.e. without the extern
storage-class), also count
as definitions. Likewise, all function local variable declarations are also
definitions regardless of whether or not you assigned a value to them.
Implementing the errno object
Since we want multiple files to be able to access the errno
object, we would
need to place the line extern int errno;
in every file that needs access to
errno
. The way we will accomplish this is by creating the header file,
errno.h
, and placing this declaration inside it. Now, any file wishing to
access errno
can just include errno.h
and it will get access. This is only
declaring the errno
object, so we also need to provide a definition for
errno
so that space is reserved for it such that the library can actually
assign it a value and other files can check that value. We will have a matching
errno.c
file with int errno;
in it providing the definition for our errno
object. It is unnecessary to initialize errno
since it is an external
variable. The standard dictates that objects with external linkage have static
storage duration (6.1.2.4 "Storage duraction of objects"), that objects in
static storage are initialized before program startup (5.1.2 "Execution
environment"), and that the value they are initialized to is 0 if no value is
provided (6.4.7 "Initialization").
So, errno.h
will contain the declaration for errno
:
extern int errno;
errno.c
will contain the definition for errno
:
int errno;
And files which want to access errno
only need to include errno.h
like so:
#include <errno.h>
EDOM and ERANGE
errno.h
must also provide these two macros which indicate two different error
conditions, defined in 7.5.1, "Treatment of error conditions". EDOM
indicates
a domain error which means that the input argument is outside the domain over
which the function is defined; e.g. if your function accepts inputs from 0 to 5
and you pass 10 to it, you've caused a domain error. ERANGE
indicates a range
error which means that the result of a function cannot be represented as a
double
value; e.g. if you try to take the log of 0 then you cause a range
error because log(0) is undefined and cannot be represented.
Since we're using macros for EDOM
and ERANGE
it doesn't really matter what
value we give them since the macro name would be used to check errno
. We will
just use the values 1 and 2 the represent EDOM
and ERANGE
, respectively.
Just like errno
, we want other files to have access to EDOM
and ERANGE
;
we'll place them in the errno.h
file so that anyone accessing errno
automatically gets access to these macros as well.
We'll add the following to errno.h
:
#define EDOM (1)
#define ERANGE (2)
Header Guards
Since we've written our first header file, we also need to talk about header
guards. When you #include
a file, the contents of that file are inserted into
the current file at the location of the #include
directive. When multiple
files include the same file, the contents get included multiple times and you
can end up with multiple definition errors.
To prevent this, we use preprocessor directives to control when the contents of the header file get used. Header guards look like the following:
#ifndef _HEADER_H
#define _HEADER_H
/* contents of header */
#endif /* _HEADER_H */
This works by checking to see if the identifier _HEADER_H
has not been defined
yet. If it hasn't been defined, in the case of the first occurrence of a file
including this header, then the code between the #ifndef
and #endif
is
processed and the identifier _HEADER_H
is defined. If the identifier
_HEADER_H
has been defined, in the case of any inclusion of the header after
the first, the code inside the conditionals is not processed. Anytime you use a
header, you should use header guards to prevent errors from occurring when the
header needs to be included by multiple files.
Identifier name
The identifier we used, _HEADER_H
, will usually correspond to the specific
name of the file that the header guards are placed in. The reason that we
prepend these with an underscore, _
, is because tokens of this naming scheme
are specifically reserved by the standard for use in the standard library.
Section 7.1.3, "Reserved identifiers" states what names are reserved, and our
identifier falls within "identifiers that begin with an underscore and either an
uppercase letter or another underscore". Specifically for our errno.h
file,
our header guards will look like:
#ifndef _ERRNO_H
#define _ERRNO_H
/* contents of header */
#endif /* _ERRNO_H */
Implementation of errno.h
That finishes up all the pieces we need for errno.h
. Combining all these
things together, our implementation of errno.h
is below (without comments):
#ifndef _ERRNO_H
#define _ERRNO_H
#define EDOM (1)
#define ERANGE (2)
extern int errno;
#endif /* _ERRNO_H */
Makefile changes
A few modifications to the Makefile were needed to facilitate building object files from C source files and to use our header directory instead of the standard system header directory.
Compiling C files into object files
This addition was pretty simple. We we're previously only assembling .s files,
but now that we have some C code, we also want to build objects from .c files.
We can add a new pattern substitution that appends more object names to the
OBJECTS
variable, like so:
OBJECTS += $(patsubst $(SRCDIR)/%.c,%.o,$(wildcard $(SRCDIR)/*.c))
Defining the system include directory
By default, gcc
will use /usr/include
as the location for finding system
header files. System headers are wrapped with <>
rather than ""
when you
#include
them. Since we want to use the header files in our ./include
directory, we need to tell gcc
not to use files found in /usr/include
. The
-nostdinc
flag tells gcc
not to search the standard system directories for
header files, exactly what we want! Now, we need to tell gcc
where to search
for system headers with the -isystem
flag. You simple provide a directory path
to this argument, so from the base of our directory structure we will use the
./include
directory by adding -isystem ./include
.
Compile for a specific standard
gcc
also includes specific rules for compiling against a certain standard.
Since we are writing this for ISO 9899:1990 we want to compile for that standard
by passing the following flag: -std=iso9899:1990
.
Testing errno.h
Since we implemented errno.h
then we need to write a test to ensure that
things work the way we want them to. This should be pretty simple because all we
need to do is #include <errno.h>
and ensure that we can use the errno
object
as well as the EDOM
and ERANGE
macros. The test I wrote looks like so:
#include <errno.h>
/*******************************************************************************
errno_test
*//**
@brief Tests the functionality of errno.h
@return Indication of success
@retval 0 Successfully ran all tests
@retval 1 Failed to run tests
*******************************************************************************/
int
errno_test(void)
{
int ret = 0;
/* Ensure errno starts out as 0 */
if (0 == errno)
{
/* Test setting errno to all defined values */
errno = EDOM;
errno = ERANGE;
/* Reset since no errors occurred */
errno = 0;
}
else
{
ret = 1;
}
return ret;
}
Link order
Lastly, we need to slightly alter the order of our build command so that our symbols will resolve properly. Initially, we used the following recipe to link our test code against the library:
$(TSTNAME): $(LIBNAME) $(TSTOBJS)
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(patsubst $(LIBNAME,,$^))
When I compiled this way, I kept getting the following errors:
errno_test.o: In function `errno_test':
errno_test.c:(.text+0xd): undefined reference to `errno'
errno_test.c:(.text+0x17): undefined reference to `errno'
errno_test.c:(.text+0x21): undefined reference to `errno'
errno_test.c:(.text+0x2b): undefined reference to `errno'
The first thing I checked was that the library actually had the errno symbol and
that it was global. You check this by dumping the symbol table with objdump
-t
:
$ objdump -t libwelibc.a
In archive libwelibc.a:
_start.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 g .text 0000000000000000 _start
0000000000000000 *UND* 0000000000000000 main
errno.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 errno.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000004 O *COM* 0000000000000004 errno
Hrm, we can see the last entry is errno and that it's placed in the .common
section of the ELF (indicated by *COM*
). The .common section holds
uninitialized global variables and is where we would expect to see errno
since
we didn't specificically initialize it in errno.c
. So, the symbol is there but
the linker is having trouble resolving it.
When linking object files together, the order in which you list them makes a
difference in how the symbols are resolved during linking. Eli Bendersky wrote
a great article explaining this, Library order in static linking. It even
has an example of this exact situation of linking against a library. The answer
to the problem is to place the library after the other object files we are
linking together. So, we just move the $(LDFLAGS)
variable after the pattern
substitution like so:
$(TSTNAME): $(LIBNAME) $(TSTOBJS)
$(CC) $(CFLAGS) $(patsubst $(LIBNAME),,$^) $(LDFLAGS) -o $@
I also moved the output file to the end because that seems to make sense to me.
Conclusion
With all that, we now know what modifiable lvalues and macros are, how to use global variables with C, and how to link properly with our own system headers. We now have the error reporting facilities we need to move on with the library. The next piece we will implement is 7.1.5, "Limits <float.h> and <limits.h>".
comments powered by Disqus