Journal Articles

CVu Journal Vol 31, #6 - January 2020 + Programming Topics
Browse in : All > Journals > CVu > 316 (11)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Static C library and GNU Make

Author: Bob Schmidt

Date: 05 January 2020 17:28:31 +00:00 or Sun, 05 January 2020 17:28:31 +00:00

Summary: Ian Bruntlett shares his experiences with using ‘make’ to build a small test project.

Body: 

My biggest problem with the GNU Make manual [1] was its lack of testable examples. This article is intended to partially redress that, by offering a very simple but expandable example. For simplicity, I am not using a TDD framework like check [2] or CppUTest [3] – that is likely to come later. The code is now available on my GitHub page [4].

My Makefile – the main bits

This is what happens when make clean is invoked:

  $ make clean
  rm -fv *.o libtrim.a test_ltrim test_rtrim
  removed 'ltrim.o'
  removed 'rtrim.o'
  removed 'libtrim.a'
  removed 'test_ltrim'
  removed 'test_rtrim'

This is what happens when make all is invoked, after a make clean:

  $ make all
  gcc -g   -c -o ltrim.o ltrim.c
  ar rvU libtrim.a ltrim.o
  ar: creating libtrim.a
  a - ltrim.o
  gcc -g -L.    test_ltrim.c -ltrim -otest_ltrim
  gcc -g   -c -o rtrim.o rtrim.c
  ar rvU libtrim.a rtrim.o
  a - rtrim.o
  gcc -g -L.    test_rtrim.c -ltrim -otest_rtrim

Show me the code

First off, a header file to list what will be in our static C library.

  // trim.h
  // (c) Ian Bruntlett, October 2019
  // trim left-hand side text from a string, 
  // in situ
  extern void ltrim(char s[]);
  
  // trim left-hand side text from a string, into 
  // a user-provided buffer
  extern void ltrimcpy(char *dest, char *src);
  
  // trim right-hand side text from a string, 
  // in situ
  extern void rtrim(char text[]);

  // trim right-hand side text from a string, 
  // into a user-provided buffer
  extern void rtrimcpy(char *dest, char *src);

This library is tiny – it is only made up of two files – ltrim.c and rtrim.c but is OK as an example. Typically, many more object files would be part of the library. Listing 1 is one of them.

// ltrim.c
// (c) Ian Bruntlett, October 2019
#include <string.h>
#include <ctype.h>
#include "trim.h"

void ltrim(char s[])
{
  if ( !isspace(s[0]) )
    {return;}
  // Get index of first non-space
  int non_space_index=0;
  while ( isspace(s[non_space_index]) )
    { ++non_space_index; }
  // copy text characters over the white space
  int dest_index=0;
  while ( s[dest_index++] =
    s[non_space_index++] )
  ;
}
void ltrimcpy(char *dest, char *src)
{
  strcpy(dest, src);
  ltrim(dest);
}
			
Listing 1

The functions in Listing 1 are used to trim white space from the left-hand side of a C string. I think they work as they have passed various tests (more on that later). The companion source file, rtrim.c (Listing 2) trims white space from the right-hand side of a C string.

// rtrim.c
// (c) Ian Bruntlett, October 2019
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "trim.h"
#define TEST_MAX (100)

void rtrim(char text[])
{
  int length = strlen(text);
  if (!length)
    return;
  for (int rhs = length-1;
       isspace(text[rhs]);
       --rhs
       )
    text[rhs] = '\0';
}
void rtrimcpy(char *dest, char *src)
{
  strcpy(dest,src);
  rtrim(dest);
}
			
Listing 2

So that is some code to test. I have two test programs, test_ltrim.c and test_rtrim.c, that I have used as test beds for the above code. Listing 3 is test_ltrim.c. If run with no parameters, it runs a built-in set of tests. Otherwise it uses two parameters from the command line (usage: test_ltrim "test string" "expected result string") for convenience.

// test_ltrim.c
// (c) Ian Bruntlett, October 2019
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "trim.h"

#define TEST_MAX (100)

void test_ltrimcpy(char *test_argument, 
  char *expected_result)
{
  char test_result[TEST_MAX];
  assert(strlen(test_argument) < TEST_MAX);
  ltrimcpy(test_result, test_argument);
  int success = !strcmp(test_result,
    expected_result);  
  if ( success )
    { printf("OK :" );  }
  else
    { printf("Failed :" );  }
  printf("argument : '%s' result : '%s' 
    : expected '%s'\n", test_argument,
    test_result, expected_result);
  assert(success);
}
int main(int argc, char *argv[])
{
  if (argc==1 )
  {
    test_ltrimcpy("  Hello ", "Hello ");
    test_ltrimcpy("Hello ", "Hello ");
    test_ltrimcpy("","");
    test_ltrimcpy("     ","");
  }
  else if (argc==3)
    test_ltrimcpy(argv[1],argv[2]);
  else
    printf("%s incorrect arguments\n", argv[0] );
  return 0;
}
			
Listing 3

So that is the code. How do we build and run it? It is possible (but error-prone) to continually type in commands to build the code prior to running it. Step forward GNU Make. It automates the build process and typically needs to be provided with a Makefile so it knows what you want it to do. Listing 4 is my Makefile.

# makefile for libtrim.a static library, using
# archive members as targets.
# (c) Ian Bruntlett, October 2019

CC = gcc
CFLAGS = -g
ARFLAGS = rvU
executables =  test_ltrim test_rtrim
tar_filename = trim-source-$(shell date "+%Y-%m-%d" ).tar

all: $(executables)

test_ltrim : test_ltrim.c trim.h libtrim.a(ltrim.o)
  $(CC) -g -L.    test_ltrim.c -ltrim -o$@

test_rtrim : test_rtrim.c trim.h libtrim.a(rtrim.o)
  $(CC) -g -L.    test_rtrim.c -ltrim -o$@

ltrim.o : ltrim.c trim.h

rtrim.o : rtrim.c trim.h

.PHONY: tar-it
tar-it:
  tar -cvf $(tar_filename) *.c *.h makefile

.PHONY: clean
clean:
  rm -fv *.o libtrim.a $(executables)
			
Listing 4

This makefile can build the test executables (test_ltrim and test_rtrim), the library modules (ltrim.o and rtrim.o), and the library itself (libtrim.a), when the command make is run. That runs the default goal which, in this case, is called all and depends upon test_ltrim and test_rtrim. Make then looks at those targets and determines that as well as those targets, both ltrim.o and rtrim.o are required and are to be stored in the static C library libtrim.a.

Makefiles

To quote the manual, Makefiles contain five kinds of things:

  1. Explicit rules, like those for test_ltrim and test_rtrim.
  2. Implicit rules that define how a source file with a particular file extension can be processed to build a destination file with another file extension. Like telling the compiler how to build object files from C source files. They tend to be customisable by setting variables such as CFLAGS and ARFLAGS. To avoid conflicts between Make’s built-in variables and user-defined variables, the user-defined variables are usually given lower-case names.
  3. Variable definitions (there are two types of variables). Typically used to specify pieces of text that are likely to be repeated. For example, in my makefile there is a line that defines the variable executables to contain the text "test_ltrim test_rtrim".
  4. Directives for more advanced Makefiles. See the manual [1].
  5. Comments – lines starting with #.

Rules

Rules tell Make how to behave. An explicit rule looks similar to this (note that some things can be left out):

  targets : prerequisites
    commands that must be indented with a TAB
    character
  all: $(executables)

The above code is an explicit rule that depends on the value of $(executables) which we know contains the two values test_ltrim and test_rtrim. So, when make all is invoked, it is akin to running make test_ltrim test_rtrim.

  test_ltrim : test_ltrim.c trim.h
  libtrim.a(ltrim.o)
    $(CC) -g -L.    test_ltrim.c -ltrim -o$@

The above rule has the target test_ltrim. Once this rule is processed, the file test_ltrim should have been created (it is an error if it isn’t). It lists the prerequisites – these are files that the target (test_ltrim) depends upon. If they are changed, the target needs to be recompiled. This rule is special in that it has the prerequisite libtrim.a(ltrim.o). Normally it would be ltrim.o. However, this alternative syntax tells make that the target depends on ltrim.o which, in turn, is to be part of an automatically generated static C library called libtrim.a. In Linux, library files are managed using the ar utility. To get the library created and updated properly, a built-in variable had to be set like this:

  ARFLAGS = rvU

The command line to build test_ltrim is this:

  $(CC) -g -L.    test_ltrim.c -ltrim -o$@

Where:

-o$@ is interesting. It states that the output executable name is the same name as the target – i.e. test_ltrim. $@ is one of many special variables that are invaluable.

The rules for determining if ltrim.o and rtrim.o need to be recompiled are this:

  ltrim.o : ltrim.c trim.h
  rtrim.o : rtrim.c trim.h

They specify that the above object files depend on their namesake C source files as well as the header, trim.h. If I had used a default rule for this, the header file would not have been considered as a prerequisite.

Helper scripts (aka Phony Targets): clean and tar-it

For convenience, it is possible to put commonly used shell commands into the Makefile which can in turn be invoked when required. In my Makefile, make clean deletes all the binary files.

  .PHONY: clean
  clean:
    rm -fv *.o libtrim.a $(executables)

This also shows that, when maintaining Makefiles, you need to be conversant with Make’s syntax and your shell’s syntax. This can be confusing.

Early on in my makefile, this variable was defined by me.

  tar_filename = trim-source-$(shell date 
    "+%Y-%m-%d" ).tar

The above line is defines a variable, $(tar_filename), which is set to some literal text and gets some of its value from running the date command. For example, if the above code was run today, $(tar_filename) would be set to the value trim-source-2019-11-03.tar. This variable is used later on in the makefile, to be used when make tar-it is invoked:

  .PHONY: tar-it
  tar-it:
    tar -cvf $(tar_filename) *.c *.h makefile

Git and Git-Hub (from my notes)

I confess, a long time ago, I wrote a very basic (but functional, nevertheless) source code sharing system called SCHOLAR (Source Code Held Online Archiving and Retrieving). Apparently this makes it harder for me to get to grips with git and GitHub (a site for hosting git repositories). This is not a tutorial. Fortunately for me, I had help from accu-general to get me going and, most recently, had help directly from Bronek Kozicki.

First I signed up for a (free) GitHub account (https://github.com). Then I started trying to do things. The next thing I should have done is use the GitHub webpage to create a new repository – called ‘trim’ for my code. I have (eventually) decided to go public on my email account and so I also went to https://github.com/settings/emails and deselected Block command line pushes that expose my email . I do hope that I have done the right thing. Another thing I should have done was these commands (your parameters will need your name and your email address):

  git config --global user.email
    "ian.bruntlett@gmail.com"
  git config --global user.name "Ian Bruntlett"

Before I could put files on GitHub, I needed a local repository. So I went to the directory I had my code in and typed:

  git init
  git status
  git add *.c
  git add *.h
  git add makefile
  git add readme.txt
  git commit -m "First commit for libtrim support
  code"
  git remote add origin https://github.com/ian-
  bruntlett/trim.git
  git remote -v

Output of the above command:

  origin  https://github.com/ian-bruntlett/trim.git
  (fetch)
  origin  https://github.com/ian-bruntlett/trim.git
  (push)
  git push -u origin master

Don’t quote me on git! Now that I have an idea of which commands are most important to me, I will be reading the man pages in depth and reading a good book about it [5].

Conclusion

I hope this has been a helpful introduction to Make and static libraries. Happy coding!

References

[1] GNU Make manual https://www.gnu.org/software/make/manual/

[2] Check – see https://libcheck.github.io/check/ (or install the check package)

[3] CppUTest – see https://cpputest.github.io/ (or install the cpputest package)

[4] https://github.com/ian-bruntlett/trim

[5] Version control with Git by Jon Loeliger & Matthew McCullough.

Ian Bruntlett On and off, Ian has been programming for some years. He is a volunteer system administrator (among other things) for a mental health charity called Contact (www.contactmorpeth.org.uk). He is learning low-level and other, higher-level, aspects of programming.

Notes: 

More fields may be available via dynamicdata ..