Aiming For Portability of Our C Programs: Next
Aiming For Portability of Our C Programs: Next
Systems Programming
Focus on the core of the language, avoiding some features that some compilers do not yet support (focussing on C99 and not C11),
Conform to the C99 language standard (avoiding the now obsolete C89, or ANSI-C standard), and
Our goal has not been to be "geeky", nor to be pedantic for the sake of it, but to learn how to develop programs that have the maximum likelihood of being
portable across a very diverse range of operating systems and hardware platforms.
It is very important, while designing and developing software, to focus on portability - our initial goal may only be to develop software for our favorite,
preferred platform, but we cannot anticipate the future direction of the software.
We may employ the C preprocessor to isolate system dependencies in our code. We employ conditional compilation of our code, permitting a single source
file to compile on different operating systems.
#if defined(__APPLE__)
#define DICTIONARY "/usr/share/dict/web2"
.....
#elif defined(__linux__)
#define DICTIONARY "/usr/share/dict/linux.words"
.....
#elif defined(_WIN32)
#define DICTIONARY "%AppData%\\Microsoft\\Spelling\\default.dic"
.....
#else
#error Cannot determine your operating system type
#endif
The C preprocessor predefines certain preprocessor tokens to identify the operating system being compiled for, the processor type, etc. Code may then
include code appropriate for that combination of tokens.
#ifdef __APPLE__
.....
#endif
#ifndef __linux__
.....
#endif
Note that the syntax for the preprocessor's #if...#elif...#else...#endif is quite different than C's if...else if...else statements.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
and probably didn't worry if you'd included them in the correct order (yes, order can be important).
If necessary, the standard header files will include other standard header files to obtain necessary definitions of constants and data types.
Consider the above example:
what if the <stdlib.h> header file needs to include the <string.h> header file?
won't our second inclusion of <string.h> result in lots of multiple re-definitions?
To protect against multiple inclusions, header files being included in the wrong order, header files may use conditional compilation to only define their
contents once. Consider this example from <string.h> :
#ifndef _STRING_H_
#define _STRING_H_
.....
#endif
Consider how these bits form bytes, and how integers are represented:
The constant NBBY is not a C99 symbol, but operating systems provide it to give the number of bits per byte.
Firstly, let's consider a few of C's operations on bits and bit patterns. When used in combination, these provide some powerful facilities not readily
supported in some other programming languages.
left shift 3 << 2 12 Shifts the bits of n left p positions. Zero bits are shifted into the low-order positions.
n << p
5 >> 2 Shifts the bits of n right p positions. If n is a 2's complement signed number, the sign bit is shifted into the high-order
right shift 1
n >> p positions.
Note: Don't confuse &&, the short-circuit logical-and, with &, which is the less common bitwise-and.
While the bitwise-and can also be used with Boolean operands, this is extremely rare and is almost always a programming error.
Consider the following example, using an unsigned int to represent colours in an RGB format of 24 bits-per-pixel (24bpp).
Here, the left-shift operator is used to quickly multiply a small value by a constant power of two.
On most modern architectures, bit-shifting operations are considerably faster than the equivalent multiplications (use left-shifting) and divisions (use right-
shifting).
www.rapidtables.com/web/color/RGB_Color.htm
www.w3schools.com/colors/colors_rgb.asp
Such code often mixes octal and hexadecimal values, and left- and right-shift operations. Fortunately, modern C compilers are able to generate the optimal
machine-level code for their targeted architecture.
Even more common is the setting and testing of individual bits in data, using each bit as if it were a complete Boolean value.
Consider the case of files in a file-system. We have already seen how we can determine if a directory entry is another directory or a
file:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
.....
struct stat stat_buffer;
if(stat(pathname, &stat_buffer) != 0)
{
perror( progname );
}
else if( S_ISDIR( stat_buffer.st_mode ))
{
printf( "%s is a directory\n", pathname );
}
else if( S_ISREG( stat_buffer.st_mode ))
{
printf( "%s is a file\n", pathname );
}
else
{
printf( "%s is unknown!\n", pathname );
}
Individual bits in this integer are then used to represent a variety of possible combinations of permissible values.
When developing multiple projects, we should strive to identify well-tested, common code may be used in each project.
Such common code should be developed in a library or an archive - implemented and compiled once, and then re-used many times.
or perhaps:
Of note: