0% found this document useful (0 votes)
60 views

Aiming For Portability of Our C Programs: Next

This document discusses techniques for improving the portability of C programs across different operating systems and hardware platforms. It describes using conditional compilation with preprocessor directives like #if and #elif to isolate system-dependent code for different operating systems. This allows a single source code file to compile on multiple systems. It also discusses using preprocessor directives in header files to prevent multiple inclusions which could cause errors. The document then covers how integers are represented internally in binary and how bitwise operators can be used to store and manipulate multiple data items in a single integer to represent things like Boolean values.

Uploaded by

Fred
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
60 views

Aiming For Portability of Our C Programs: Next

This document discusses techniques for improving the portability of C programs across different operating systems and hardware platforms. It describes using conditional compilation with preprocessor directives like #if and #elif to isolate system-dependent code for different operating systems. This allows a single source code file to compile on multiple systems. It also discusses using preprocessor directives in header files to prevent multiple inclusions which could cause errors. The document then covers how integers are represented internally in binary and how bitwise operators can be used to store and manipulate multiple data items in a single integer to represent things like Boolean values.

Uploaded by

Fred
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

CITS2002

Systems Programming

1 next CITS2002 CITS2002 schedule

Aiming for portability of our C programs


In this unit we have focused on writing our C programs that:

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

Focus on readability of the source code.

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.

CITS2002 Systems Programming, Lecture 22, p1, 26th October 2017.


CITS2002 Systems Programming

prev 2 next CITS2002 CITS2002 schedule

Isolating inhibitors to portability - conditional compilation


Where possible, we should use features of C, itself, to identify and isolate code that may prevent portability.

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.

Try: cc -dM -E file.c

There are also some harder to read equivalents:

#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.

CITS2002 Systems Programming, Lecture 22, p2, 26th October 2017.


CITS2002 Systems Programming

prev 3 next CITS2002 CITS2002 schedule

Isolating inhibitors to portability - "protecting" our code


We may also use the C preprocessor's conditional directives to ensure that header files are not accidentally included twice.
When coding your first programs, you probably included a few of the standard header files:

#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

CITS2002 Systems Programming, Lecture 22, p3, 26th October 2017.


CITS2002 Systems Programming

prev 4 next CITS2002 CITS2002 schedule

Integers and their representation


All data in digital computers is stored as a sequence of bits, with (modern) computers aggregating 8 bits to a byte.
An integer on a Pentium, for example, requires 4 bytes, or 32 bits.

Consider how these bits form bytes, and how integers are represented:

Produces the output:


#include <stdio.h>
#include <sys/types.h> 0: 00000000000000000000000000000000 00000000 0x0000
#include <sys/param.h> 1: 00000000000000000000000000000001 00000001 0x0001
2: 00000000000000000000000000000010 00000002 0x0002
char *binary(unsigned int x) 3: 00000000000000000000000000000011 00000003 0x0003
{ 4: 00000000000000000000000000000100 00000004 0x0004
#define NBITS ((sizeof x) * NBBY) 5: 00000000000000000000000000000101 00000005 0x0005
6: 00000000000000000000000000000110 00000006 0x0006
static char bits[NBITS+1]; 7: 00000000000000000000000000000111 00000007 0x0007
char *b = bits; 8: 00000000000000000000000000001000 00000010 0x0008
9: 00000000000000000000000000001001 00000011 0x0009
for(int n=NBITS-1 ; n>=0 ; --n) 10: 00000000000000000000000000001010 00000012 0x000a
{ 11: 00000000000000000000000000001011 00000013 0x000b
*b = ((x & (1<<n)) ? '1' : '0'); 12: 00000000000000000000000000001100 00000014 0x000c
++b; 13: 00000000000000000000000000001101 00000015 0x000d
} 14: 00000000000000000000000000001110 00000016 0x000e
*b = '\0'; 15: 00000000000000000000000000001111 00000017 0x000f
return bits; 16: 00000000000000000000000000010000 00000020 0x0010
}
void display(unsigned int x)
{
printf("%2u:", x); // in decimal, base 10
printf(" %s", binary(x)); // in binary, base 2
printf(" %08o", x); // in octal, base 8
printf(" 0x%04x", x); // in hexadecimal, base 16
printf("\n");
}
int main(int argc, char *argv[])
{
for(unsigned int x=0 ; x<=16 ; ++x)
{
display(x);
}
return 0;
}

The constant NBBY is not a C99 symbol, but operating systems provide it to give the number of bits per byte.

CITS2002 Systems Programming, Lecture 22, p4, 26th October 2017.


CITS2002 Systems Programming

prev 5 next CITS2002 CITS2002 schedule

Bitwise operators in C99


Knowing that data, such as integers, are sequences of bits, we may use the multiple bits of an integer to represent multiple pieces of data in a single
integer.

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.

The bitwise operators

Name Example Result Description


bitwise-and 3 & 5 1 1 if both bits are 1.
bitwise-or 3 | 5 7 1 if either bit is 1.
exclusive-or (xor) 3 ^ 5 6 1 if both bits are different.
not ~3 -4 Inverts the bits.

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.

CITS2002 Systems Programming, Lecture 22, p5, 26th October 2017.


CITS2002 Systems Programming

prev 6 next CITS2002 CITS2002 schedule

Storing multiple items in an integer


The most frequently seen example of C's bitwise operators, is the use of the left-shift operator to store multiple "items" in a single integer variable.

Consider the following example, using an unsigned int to represent colours in an RGB format of 24 bits-per-pixel (24bpp).

typedef unsigned int RGBCOLOUR;

RGBCOLOUR set_rgb(char red, char green, char blue)


{
return (red << 16) + (green << 8) + blue;
}
....
RGBCOLOUR white = set_rgb(255, 255, 255);
RGBCOLOUR black = set_rgb( 0, 0, 0);
RGBCOLOUR skyblue = set_rgb(135, 206, 235);
RGBCOLOUR yellow = set_rgb(255, 255, 0);

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).

If unfamiliar with RGB colour representations, you may like to read:

www.rapidtables.com/web/color/RGB_Color.htm
www.w3schools.com/colors/colors_rgb.asp

CITS2002 Systems Programming, Lecture 22, p6, 26th October 2017.


CITS2002 Systems Programming

prev 7 next CITS2002 CITS2002 schedule

Extracting multiple items from an integer


Storing multiple bit-wise items in an integer is simple.

Recovery of the individual components is more difficult (to understand).

There are a wide variety of ways to undertake this:

typedef unsigned int RGBCOLOUR;


char get_blue(RGBCOLOUR rgb)
{
return (rgb & 0xff);
}
char get_green(RGBCOLOUR rgb)
{
return ((rgb >> 8) & 0xff);
}

char get_red(RGBCOLOUR rgb)


{
return ((rgb >> 16) & 0xff);
}

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.

CITS2002 Systems Programming, Lecture 22, p7, 26th October 2017.


CITS2002 Systems Programming

prev 8 next CITS2002 CITS2002 schedule

Extracting multiple items from an integer, continued


In efforts to extract even more speed from programming involving bit-wise operations, advanced programmers will often use C preprocessor macros,
providing inline functions, to reduce the (tiny) cost of the function call overhead:

typedef unsigned int RGBCOLOUR;


#define set_rgb(r, g, b) (((b) << 16) + ((g) << 8) + (r)))
#define get_blue(rgb) ((rgb ) & 0xff)
#define get_green(rgb) (((rgb) >> 8) & 0xff)
#define get_red(rgb) (((rgb) >> 16) & 0xff)
void RGB_to_greyscale(RGBCOLOUR *greypixels[], RGBCOLOUR *colourpixels[], int width, int height)
{
for(int w=0 ; w<width ; ++w) {
for(int h=0 ; h<height ; ++h) {
RGBCOLOUR thispixel = colourpixels[w][h];
int grey = (0.3 * get_red (thispixel)) +
(0.6 * get_green(thispixel)) +
(0.1 * get_blue (thispixel)) ;
greypixels[w][h] = set_rgb(grey, grey, grey);
}
}
}

CITS2002 Systems Programming, Lecture 22, p8, 26th October 2017.


CITS2002 Systems Programming

prev 9 next CITS2002 CITS2002 schedule

Setting arbitrary bits in integers


The previous examples, using RGB colour values, only set individual bytes of the integers being considered.

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 );
}

How is this being achieved?

CITS2002 Systems Programming, Lecture 22, p9, 26th October 2017.


CITS2002 Systems Programming

prev 10 next CITS2002 CITS2002 schedule

Setting arbitrary bits in integers, continued


As is typical of many operating system activities, information such as a file's filetype and access permissions are stored using multiple bits in an
integer.

Individual bits in this integer are then used to represent a variety of possible combinations of permissible values.

These may be located by "wading" through a number of system-specific files in /usr/include :

// DEFINE A "NEW" TYPE TO REPRESENT A FILE'S MODE


typedef unsigned int mode_t;
struct stat {
....
mode_t st_mode;
....
}

#define S_IFMT 0170000 // THESE BITS DETERMINE FILE TYPE


// File types
#define S_IFDIR 0040000 // DIRECTORY
....
#define S_IFREG 0100000 // REGULAR FILE
// MACROS FOR TESTING FOR DIFFERENT FILE TYPES
#define S_ISDIR(mode) (((mode) & S_IFMT) == (S_IFDIR))
....
#define S_ISREG(mode) (((mode) & S_IFMT) == (S_IFREG))

This C program may better explain these concepts: showmode.c

CITS2002 Systems Programming, Lecture 22, p10, 26th October 2017.


CITS2002 Systems Programming

prev 11 CITS2002 CITS2002 schedule

Building a library of common functions


Standard installations of a C99 compiler will also include the standard C library (providing, for example, many string handling functions), the standard
mathematics library (providing, for example, the trigonometric functions), and many additional libraries (such as for networking, GUIs, image processing,
cryptography...).

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.

cc -std=c99 -Wall -pedantic -Werror -c globals.c


cc -std=c99 -Wall -pedantic -Werror -c io.c
cc -std=c99 -Wall -pedantic -Werror -c calc.c
ar rc libmyfunctions.a globals.o io.o calc.o

We can then use our library, named libmyfunctions.a in multiple projects:

cc -std=c99 -Wall -pedantic -Werror -o proj1 proj1.c libmyfunctions.a


cc -std=c99 -Wall -pedantic -Werror -o proj2 proj2.c libmyfunctions.a

or perhaps:

cc -std=c99 -Wall -pedantic -Werror -c proj3.c


cc -o proj3 proj3.o -L. -lmyfunctions

Of note:

cc's -L option provides directory names where libraries are to be found.


cc's -l option provides the abbreviated names of libraries to be used.
all C programs require the standard C library, and so we don't need to add -lc ourselves,
and on OS-X we don't need to add -lm.

CITS2002 Systems Programming, Lecture 22, p11, 26th October 2017.

You might also like