1. Homepage of Dr. Zoltán Porkoláb
    1. Home
    2. Archive
  2. Teaching
    1. Timetable
    2. Bolyai College
    3. C++ (for mathematicians)
    4. Imperative programming (BSc)
    5. Multiparadigm programming (MSc)
    6. Programming (MSc Aut. Sys.)
    7. Programming languages (PhD)
    8. Software technology lab
    9. Theses proposals (BSc and MSc)
  3. Research
    1. Sustrainability
    2. CodeChecker
    3. CodeCompass
    4. Templight
    5. Projects
    6. Conferences
    7. Publications
    8. PhD students
  4. Affiliations
    1. Dept. of Programming Languages and Compilers
    2. Ericsson Hungary Ltd

The C programming language – Lecture 1.

History of the C language

C (/ˈsiː/, as in the letter c) is a general-purpose, imperative computer programming language, supporting structured programming, lexical variable scope and recursion, while a static type system prevents many unintended operations. By design, C provides constructs that map efficiently to typical machine instructions, and therefore it has found lasting use in applications that had formerly been coded in assembly language, including operating systems, as well as various application software for computers ranging from supercomputers to embedded systems.

(from wikipedia)

Origins

                                  -> D
Assembly -> BCPL -> B -> C -> C++ -> Java
                                  -> C#

Timeline

  • 1969 Ken Thompson develops B (a simplified BCPL)
  • 1969- Ken Thompson, Dennis Ritchie and others work on UNIX
  • 1972 Dennis Ritchie develops C
  • 1972-73 UNIX kerner is rewritten in C
  • 1977 Johnsons Portable C Compiler 1977
  • 1978 Brian Kernighan & Dennis Ritchie: The C Programming Language book
  • 1989 ANSI C standard (C90) (32 keywords)
  • 1999 ANSI C99 standard (+5 keywords)
  • 2011 ANSI C11 standard (+7 keywords)

Compiling, linking

  preprocessing   compiling         linking   executing

header      source      object       library

  a.h
  b.h   ->    b.c    ->   b.o  ---------|
                                        ----->    a.out (b.exe)
  e.h                                   |            |
  f.h   ->    d.c    ->   d.o  ---------|            |runtime
                                        |            |
  g.h   ->    g.c    ->   g.o           |            |
  h.h   ->    h.c    ->   h.o    ->   h.a  (h.lib)   |
                                       archive       |
  i.h   ->    i.c    ->   i.o                        |
  j.h   ->    j.c    ->   j.o    ->   j.so (j.dll) --|
                                      shared object

First C program: hello world

$ cat hello.c
1
2
3
4
5
6
#include <stdio.h>
int main()
{
  printf( "hello world\n" );
  return 0;   /* not strictly necessary since C99 */
}

Compiling, linking, executing

# compile + link
$ gcc  hello.c

# execute
$ ./a.out


# compile + link + set warnings on
$ gcc -ansi -pedantic -Wall -W hello.c


# c11 mode
$ gcc -std=c11 -ansi -pedantic -Wall -W hello.c


# set output name to a.exe
$ gcc -std=c11 -ansi -pedantic -Wall -W hello.c -o a.exe


# compile only
$ gcc -c  hello.c
$ ls
hello.o


# will call the linker 
$ gcc hello.o


# calls the compiler for all sources then calls the linker
$ gcc a.c b.c d.o e.a f.so

Compiler errors, warnings

If we make a syntax error, the compiler emits error(s):

1
2
3
4
5
6
7
8
9
10
/*
 *  BAD VERSION !!!
 *  Missing semicolon
 */
#include <stdio.h>
int main()
{
  printf("hello world\n") // missing ;
  return 0;
}
$ gcc -ansi -pedantic -W -Wall m.c 
m.c: In function ‘main’:
m.c:6:28: error: expected expression before ‘/’ token
   printf("hello world\n") // missing ;
                            ^

If we make a mistake, that the compiler still can compile the source we got warning(s):

1
2
3
4
5
6
7
8
9
10
/*
 *  BAD VERSION !!!
 *  Missing header
 */
// #include <stdio.h>
int main()
{
  printf("hello world\n");
  return 0;
}
$ gcc -ansi -pedantic -W -Wall -std=c11 hello2.c -c
hello2.c: In function ‘main’:
hello2.c:6:3: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
   printf("hello world\n");
   ^
hello2.c:6:3: warning: incompatible implicit declaration of built-in function ‘printf’

Warnings are serious things in C, you should treat them as errors unless you are absolute sure in yourself. Even there, it is a good habit to write warning-free code.

Second C program: Fahrenheit to Celsius conversion

(aka. The Bad, the Ugly and the Good)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 *  BAD VERSION !!!
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 25F  
 */
#include <stdio.h>
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 25 )
  {
    printf( "Fahr = %d\t,Cels = %d\n", fahr, 5/9*(fahr-32) );
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall -std=c11 fahrenheit.c -o fahrenheit

$ ./fahrenheit
Fahr = -100	,Cels = 0
Fahr = -75	,Cels = 0
Fahr = -50	,Cels = 0
Fahr = -25	,Cels = 0
Fahr = 0	,Cels = 0
Fahr = 25	,Cels = 0
Fahr = 50	,Cels = 0
Fahr = 75	,Cels = 0
Fahr = 100	,Cels = 0
Fahr = 125	,Cels = 0
Fahr = 150	,Cels = 0
Fahr = 175	,Cels = 0
Fahr = 200	,Cels = 0
Fahr = 225	,Cels = 0
Fahr = 250	,Cels = 0
Fahr = 275	,Cels = 0
Fahr = 300	,Cels = 0
Fahr = 325	,Cels = 0
Fahr = 350	,Cels = 0
Fahr = 375	,Cels = 0
Fahr = 400	,Cels = 0

The reason is that in strongly typed programming languages the compiler decides the type of all (sub)expressions in compilation time, and that depends only from the type of the operands (and from the operator), not from the value of the operands. Thus 5/9 is always integer (with value 0).

Let try to fix it using 5./9. istead of 5/9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 *  BAD VERSION !!!
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 25F  
 */
#include <stdio.h>
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 25 )
  {
    printf( "Fahr = %d\t,Cels = %d\n", fahr, 5./9.*(fahr-32) );
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall -std=c11 fahrenheit.c -o fahrenheit
fahrenheit.c: In function ‘main’:
fahrenheit.c:17:5: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ [-Wformat=]
     printf( "Fahr = %d,\tCels = %d\n", fahr, 5./9.*(fahr-32) );
     ^

$ ./fahrenheit 
Fahr = -100,	Cels = 913552376
Fahr = -75,	Cels = -722576928
Fahr = -50,	Cels = -722576928
Fahr = -25,	Cels = -722576928
Fahr = 0,	Cels = -722576928
Fahr = 25,	Cels = -722576928
Fahr = 50,	Cels = -722576928
Fahr = 75,	Cels = -722576928
Fahr = 100,	Cels = -722576928
Fahr = 125,	Cels = -722576928
Fahr = 150,	Cels = -722576928
Fahr = 175,	Cels = -722576928
Fahr = 200,	Cels = -722576928
Fahr = 225,	Cels = -722576928
Fahr = 250,	Cels = -722576928
Fahr = 275,	Cels = -722576928
Fahr = 300,	Cels = -722576928
Fahr = 325,	Cels = -722576928
Fahr = 350,	Cels = -722576928
Fahr = 375,	Cels = -722576928
Fahr = 400,	Cels = -722576928

Still bad. Now the value of the celsius is computed correctly, but we try to print it as an integer: using %d format. What happened: we placed (a likely 8 byte) double to the stack, but used only 4 bytes (interpreted as integer) to print. Obviously, this is wrong.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 *  UGLY VERSION
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 25F  
 */
#include <stdio.h>
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 25 )
  {
    printf( "Fahr = %d,\tCels = %f\n", fahr, 5./9.*(fahr-32) );
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall -std=c11 fahrenheit.c -o fahrenheit

$ ./fahrenheit 
Fahr = -100,	Cels = -73.333333
Fahr = -75,	Cels = -59.444444
Fahr = -50,	Cels = -45.555556
Fahr = -25,	Cels = -31.666667
Fahr = 0,	Cels = -17.777778
Fahr = 25,	Cels = -3.888889
Fahr = 50,	Cels = 10.000000
Fahr = 75,	Cels = 23.888889
Fahr = 100,	Cels = 37.777778
Fahr = 125,	Cels = 51.666667
Fahr = 150,	Cels = 65.555556
Fahr = 175,	Cels = 79.444444
Fahr = 200,	Cels = 93.333333
Fahr = 225,	Cels = 107.222222
Fahr = 250,	Cels = 121.111111
Fahr = 275,	Cels = 135.000000
Fahr = 300,	Cels = 148.888889
Fahr = 325,	Cels = 162.777778
Fahr = 350,	Cels = 176.666667
Fahr = 375,	Cels = 190.555556
Fahr = 400,	Cels = 204.444444

This works, but the input is not nicely formatted. Also, the complex expression inside the printf expression is not easy to understand or maintainde. We refactor the Fahrenheit to Celsius conversion to a separate function.

Recognize, that the signature of the function is double fahr2cels(double) and we pass an integer parameter. This is ok, since we declared the full prototype of the function, and the integer will be converted to double on parameter passing. This happens only when we declare the parameterlist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 *  OK
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 25F  
 */
#include <stdio.h>
double fahr2cels( double f)
{./
  return 5./9. * (f-32);
}
int main()
{
  int fahr;

  for ( fahr = -100; fahr <= 400; fahr += 25 )
  {
    printf( "Fahr = %4d,\tCels = %7.2f\n", fahr, fahr2cels(fahr) );
  }
  return 0;
}
$ gcc -ansi -pedantic -W -Wall -std=c11 fahrenheit.c -o fahrenheit

$ ./fahrenheit 
Fahr = -100,	Cels =  -73.33
Fahr =  -75,	Cels =  -59.44
Fahr =  -50,	Cels =  -45.56
Fahr =  -25,	Cels =  -31.67
Fahr =    0,	Cels =  -17.78
Fahr =   25,	Cels =   -3.89
Fahr =   50,	Cels =   10.00
Fahr =   75,	Cels =   23.89
Fahr =  100,	Cels =   37.78
Fahr =  125,	Cels =   51.67
Fahr =  150,	Cels =   65.56
Fahr =  175,	Cels =   79.44
Fahr =  200,	Cels =   93.33
Fahr =  225,	Cels =  107.22
Fahr =  250,	Cels =  121.11
Fahr =  275,	Cels =  135.00
Fahr =  300,	Cels =  148.89
Fahr =  325,	Cels =  162.78
Fahr =  350,	Cels =  176.67
Fahr =  375,	Cels =  190.56
Fahr =  400,	Cels =  204.44

There are still room to improve. To help maintenance, we can lift out the magic constants from the source to the head of the program.

In this version we use preprocessor directives to define constants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
 *  OK, with #define
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 25F  
 */
#include <stdio.h>
#define LOWER -100
#define UPPER  400
#define STEP    25
double fahr2cels( double f)
{
  return 5./9. * (f-32);
}
int main()
{
  int fahr;

  for ( fahr = LOWER; fahr <= UPPER; fahr += STEP )
  {
    printf( "Fahr = %4d,\tCels = %7.2f\n", fahr, fahr2cels(fahr) );
  }
  return 0;
}

In this version we use named constants to define the values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
 *  OK, with const
 *  Convert Fahrenheit to Celsius
 *  between -100F and +400F  by 25F  
 */
#include <stdio.h>
const int lower = -100;
const int upper =  400;
const int step  =   25;
double fahr2cels( double f)
{
  return 5./9. * (f-32);
}
int main()
{
  int fahr;

  for ( fahr = lower; fahr <= upper; fahr += step )
  {
    printf( "Fahr = %4d,\tCels = %7.2f\n", fahr, fahr2cels(fahr) );
  }
  return 0;
}

##Homework:

  1. Create a program printing your name. Compile, link, run.

  2. Separate the program to 2 sources: one return the name, other prints Hints: - use char *my_name() as a function prototype. - use “%s” as a format string for printf