Writing in C: Part 2, Where Things Get Strange


There's strictly no warranty for the correctness of this text. You use any of the information provided here at your own risk.


Contents:

  1. Preface to Page Two
  2. Working with Data in Memory.
  3. Pointers and the Function "malloc()"
  4. Pointers and Functions in General. Function "free()".
  5. Pointers to Simpler Datatypes. Dereferentiation.
  6. Pointing Pointers to Already Allocated Memory. Pointer Arithmetic.
  7. realloc()
  8. sizeof()
  9. Comparing Pointers. NULL-Pointers
  10. Pointer-Casting. Pointers to void
  11. String Literals in the Code, and Better Not Pointing a Pointer to Them
  12. String Functions
  13. Strings with More than one Line
  14. Getting User-Input
  15. Example-Program "Cookie Monster"
  16. Structures with struct
  17. Pointers to Structures
  18. Emulating Object-Oriented Programming in C
  19. Unions
  20. Arrays
  21. Memory Areas of C-programs
  22. Linked Lists (Dynamically Changeable Lists)
  23. Getting a Random Number
  24. Alternative Syntaxes
  25. Working with Files
  26. Keyword Summary of Variable Datatypes


1. Preface to Page Two

So far, my first text was dealing with the language constructions of C, that are relatively easy to understand. But now we're reaching the point, where things may get strange and are at first very unfamiliar.
The books I read then just went on describing the language systematics. They didn't get to the point, what C was about. So I'm taking a different approach here. I'm going straight to managing memory yourself, and move on from there to emulating objects. In the end, when everything is explained, you may find, that the books were basically talking about the same things.

The first chapters on this page build on top of each other. So please read them one after one, starting with the first one. Until you reach "Emulating Object-Oriented Programming in C". This is probably the most interesting chapter, but it can only be understood, if you're familiar with everything that was explained before.


2. Working with Data in Memory.

In programming, often more complex data than single ints and chars have to be processed.
In C, the programmer has to manage the memory of the program himself / herself. So the common way to deal with that kind of data in C is as follows:

In the following chapter, I describe, how this is done.


3. Pointers and the Function "malloc()"

To do, what was mentioned in the last chapter, we need a variable that can access data in memory. C has such a variable, and it is called a "pointer". When declaring it, also the type of data it points to has to be specified, so for example a pointer to "int" called "p" is declared as:

int *p;

A pointer to "char" called "p" is declared as:

char *p;

Where "p" is of datatype "int *", respectively "char *".

And we need a way to reserve memory for data. The function "malloc()" (= "memory allocation"), which is part of "stdlib.h" can do this. The number of bytes, that should be reserved, are passed as an argument to "malloc()". If the function works, the memory is allocated. (If not, "malloc()" returns "NULL". It should be checked, if "malloc()" was successful.)
So to allocate 100 bytes to store 100 "char", and get a pointer to that memory, one would write:

char *p = malloc(100);

Now that we have that memory, and we have a pointer to access it, we can do something with the memory. As specified, there can to be up to 100 "char" in there. We could for example do now:

p[0] = 'H';
p[1] = 'e';
p[2] = 'l';
p[3] = 'l';
p[4] = 'o';
p[5] = '\0';

print it:

printf("%s\n", p)

And free the memory in the end as mentioned above:

free(p);

Altogether:

#include <stdio.h>
#include <stdlib.h>

void main(void) {
    char *p = malloc(100);
    p[0] = 'H';
    p[1] = 'e';
    p[2] = 'l';
    p[3] = 'l';
    p[4] = 'o'; 
    p[5] = '\0';
    printf("%s\n", p);
    free(p);
}

Notice, on our way we just defined a string here. Strings in C are just a couple of char-bytes in memory, terminated by the 0-char (which is written as "'\0'").

Many functions take a pointer as an argument. So does "printf()" here, when used with the format-string "%s".

Another function, that takes a pointer as an argument is "strcpy()", which is part of "string.h". It copies text into a memory location, pointed to by a pointer. Using it, we can write:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void main(void) {
    char *p = malloc(100);
    strcpy(p, "Hello");
    printf("%s\n", p);
    free(p);
}

The "Hello" is a "string literal" here, pure text inside a C-program. Such "string literals" are stored somewhere in a read-only area of the memory used by the C-program.


4. Pointers and Functions in General. Function "free()".

Pointers can passed to and from functions without limitations. That way, the memory, the pointers point to, can be accessed inside the functions, and the data, that is stored there, can be processed by the functions.
It is also possible, to allocate new memory inside a called function, and return the pointer to that memory from the called function to the calling function.
It's not necessary to call "free()" in the same function, where the memory was allocated. "free()" can be called from anywhere in the program. It just frees the memory, to which the pointer, that is passed to it, points. Example:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *createData(void) {
    char *p = malloc(100);
    strcpy(p, "Hello");
    return p;
}

void main(void) {
    char *a = createData();
    printf("%s\n", a);
    free(a);
}

Second example:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void printAndFree(char *p) {
    printf("%s\n", p);
    free(p);
}

void main(void) {
    char *a = malloc(100);
    strcpy(a, "Hello");
    printAndFree(a);
}


5. Pointers to Simpler Datatypes. Dereferentiation.

Pointers can also be pointed to simpler datatypes such a single int or a single char.
It mustn't be set to the int/char itself though, but to its memory address, which is indicated by a "&":

int a = 5;
int *p = &a;

As already mentioned above, the datatype of the "pointer to int" called "p" is "int *". So the second line could also be written as:

int *p;
p = &a;

When the data, the pointer points to, should be accessed (not the memory address, it points to) the pointer has to be "dereferenced". This is done by writing a leading "*":

printf("%d\n" *p);

Once again:

#include <stdio.h>

int main(void) {
    int a = 5;
    /* Can also be written as: "int *p = &a;" */
    int *p;
    p = &a;
    printf("%d\n", *p);
    return 0;
}

Often, functions take the pointer itself as an argument. Then, just "p" has to be passed. This is for example the case for functions like "strcpy()" or for "printf()" with the format-string "%s".
"printf()" with the format-string "%d" is different though. It expects an integer as its argument. Therefore, not the "pointer to int" called "p" itself can be passed to it, but the data it points to - which is accessed by dereferencing the pointer and therefore passing "*p".

Notice, that when using smaller datatypes like single ints, chars or also pointers themselves, it's not necessary to call "malloc()" to reserve memory for them. The information, how much memory is needed for them, is stored in the compiler, and the compiler allocates this memory itself accordingly. So in a sense, having to allocate memory for data yourself is the ordinary case, while using ints and chars without allocating memory is the exception, made possible by the compiler.


6. Pointing Pointers to Already Allocated Memory. Pointer Arithmetic.

As might be expected, pointers can also be pointed to already allocated memory.

And it's possible, to use the operators "+=", "++", "-=" and "--" on pointers. This is called "pointer arithmetic". The effect is, that the memory location of the pointer is changed accordingly. For example, if there's a pointer "p", the operation "p++;" makes it point one byte next to the original byte.
That way, for example a pointer can be used to "walk" on the data of a string and change characters. You just have to remember, where the string began, so it can be freed correctly. This can be done, by keeping a pointer at the original position.
There's also the socalled "array notation", with a number inside square brackets: It keeps the pointer at its location, but processes the byte, the number of bytes in the brackets away:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char *a = malloc(100);
    strcpy(a, "Hello XorYd");
    puts(a);
    /* Pointing a new pointer to the location, "a" points to: */
    char *b = a;
    b += 6;
    *b = 'W';
    puts(a);
    char *c = a;
    c[9] = 'l';
    puts(a);
    free(a);
    return 0;
}


7. realloc()

If you want a different size of the memory, that was already allocated with "malloc()", you can use the function "realloc()":

char *a = malloc(20);
a = realloc(a, 100);
free(a);

"realloc()" takes a pointer to the old allocated memory, and returns a pointer to newly allocated memory of a different size.


8. sizeof()

A single byte with its 8 bits can store a number from 0 to 255. But for example an "int" in C can be much larger than 255. Therefore, a single "int", occupies more than one byte in memory. "malloc()" needs the number of bytes to allocate in memory as an argument though. Therefore, the number of bytes to pass to "malloc()" to store one or more ints, has to be calculated. The operator "sizeof()" (that works similar to a function) can be used for that:

/* Allocating memory for 1 and for 7 ints: */
int *a = malloc(sizeof(int));
int *b = malloc(7 * sizeof(int));
...
...
free(a);
free(b);

"sizeof()" doesn't need to be used with "char", because one "char" has a size of one byte.


9. Comparing Pointers. NULL-Pointers

With the format-string "%p" of "printf()", the memory location, a pointer points to, can be printed. As far as I know, when two pointers are compared in an "if"-statement, the memory location, they point to, is compared. This may come in handy later.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void checkPointers(char *a, char *b) {
    printf("Pointer 1: %p\tPointer 2: %p\n", a, b);
    if (a == b) {
        puts("Pointers equal.");
    } else {
        puts("Pointers not equal.");
    }
}

int main(void) {
    char *a = malloc(100);
    char *b = malloc(100);
    strcpy(a, "Hello");
    strcpy(b, "Hello");
    checkPointers(a, b);
    if (strcmp(a, b) == 0) {
        puts("The strings are equal though.");
    }
    puts("");
    /* Pointing c at the location of b: */
    char *c = b;
    checkPointers(b, c);
    free(a);
    free(b);
    return 0;
}

There is also the pointer "NULL", that points to nothing ("char *a = NULL;"). Functions like for example "malloc()" return this special pointer on failure. This can be checked ("if (a == NULL) { ... }").


10. Pointer-Casting. Pointers to void

Pointers, that point to certain data, for example to chars, can be changed lateron to point to a different datatype - while still pointing at the same memory location. This changing of datatype is called "casting". It is done like that, by putting round brackets around the datatype of the pointer:

char *a = malloc(100);
(int *) a;

And there's also the "pointer to void". It is a somehow generic pointer. Pointers can be casted from pointers to other datatypes to "pointer to void", and back. Without losing the data, because as mentioned they still point to the same memory location.
The type "pointer to void" can be used as a function parameter, to pass different types of pointers into the function. In the function, the pointers have to be cast back into their original types, to be able to work with them:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void passEverythingIn(void *p, char *type) {
    if (strcmp(type, "Pointer to int") == 0) {
        printf("%d\n", *((int *) p));
    }
    if (strcmp(type, "Pointer to char (string)") == 0) {
        printf("%s\n", (char *) p);
    }
}

int main(void) {
    int a = 5;
    int *b = &a;
    char *c = malloc(100);
    strcpy(c, "Hello");
    passEverythingIn(b, "Pointer to int");
    passEverythingIn(c, "Pointer to char (string)");
}

So this is very useful. Or the other way around: By returning a "pointer to void" from a function, a pointer to a memory location of not yet specified data can be passed. Actually, this is, what "malloc()" returns: A "pointer to void".

Casting is also possible with ordinary datatypes like "int" and "char". But it's more often used with pointers.


11. String Literals in the Code, and Better Not Pointing a Pointer to Them

"Literals" are pure texts, that the programmer writes into the code. There are "integer literals" like 5 or 25223, and string literals like "Hello". These literals are automatically stored in a memory region of the program, that can't be changed. As a result, literals themselves can't be changed.
It is possible though, to point a pointer to such a literal, especially to a string literal. But the pointer then points to that unchangeable memory region, that was automatically created by the processes of running the program.
So, technically C makes it possible to point a pointer at a string literal:

char *a = "Hello";

But that isn't the right way to initialize a string. Unless the string is known to stay unchanged throughout the whole program.
In all other cases, free and changeable memory has to be allocated for the string (or there will be difficult to find errors in the program). So with using a pointer, the string has to be initialized like this:

char *a = malloc(6);
strcpy(a, "Hello");
...
free(a)


12. String Functions

After strings have been initialized correctly, and sufficient memory has been allocated for them, things get a little easier again.
Then, functions can be used on them, that are defined in "string.h". Essential functions are:

The library "stdlib.h" also has the function:

The library "stdlib.h" has a function, that does the opposite:

Here's an example on how to use some of these functions:

#include <stdio.h>
#include <string.h>

int main(void) {
    char *a = malloc(20);
    char *b = malloc(20);
    int c = 34;
    int d = 17;
    strcpy(a, "Hello");
    printf("%d\n", strlen(a));
    strcpy(b, "World");
    printf("%s\n", a);
    printf("%s\n", b);
    strcat(a, " ");
    strcat(a, b);
    printf("%s\n", a);
    strcpy(a, "Area ");
    sprintf(b, "%d", c + d);
    strcat(a, b);
    printf("%s\n", a);
    free(a);
    free(b);
    return 0;
}


13. Strings with More than one Line

If you want to write longer strings with many lines, you can use one of the following syntaxes:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main(void) {
    char *s  = malloc(100);
    char *s2 = malloc(100);
    /* First syntax: */
    strcpy(s, "This is the first line,\n\
this is the second line.\n");

    /* Second syntax: */
    strcpy(s2, "This is the third line,\n"
               "this is the fourth line.\n");
    printf("%s", s);
    puts("");
    printf("%s", s2);
    free(s);
    free(s2);
    return 0;
}


14. Getting User-Input

In C-libraries, several functions for getting user-input can be found. There are functions like "scanf()", "fgets()", "getline()" and "getchar()". Somehow, most of these functions have drawbacks. "getchar()" reads just one character at a time, but seems to be relatively reliable. I wrote something on my own with it, which kind of works, for strings of a size up to about "bufflen", which is 100 here:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *my_input(char *s) {
    printf(s);
    int bufflen = 100;
    char *a = malloc(bufflen);
    char c;
    int count = 0; 
    c = getchar();
    while (c != EOF && c != '\n' && count < bufflen - 1) {
        a[count] = c; 
        count++;
        c = getchar();
    }    
    a[count] = '\0';
    return a;
}

int main(void) {
    char *a = my_input("Please enter a line: ");
    printf("You entered the string \"%s\", which has %d characters.\n", a, strlen(a));
    free(a);
    return 0;
}


15. Example-Program "Cookie Monster"

A nice little program from a good book about Perl called "Laura Lemay: 'Sams Teach Yourself Perl in 21 Days'" is called "Cookie Monster". If you are able to write this program in a programming language, you already know some of its constructions:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* Function "my_input()": See above: char *my_input(char *s) { ... } */

int main(void) {
    /* Cookie Monster */
    char *cookies = malloc(100);
    strcpy(cookies, "");
    while (strcmp(cookies, "COOKIES") != 0) {
        free(cookies);
        cookies = my_input("I want COOKIES: ");
    }
    puts("Mmmm. COOKIES.");
    free(cookies);
}


16. Structures with struct

Structures are containers, that can hold data of different datatypes. As we will see, structures can be used to create something similar to objects in object-oriented programming. In this sense, structures hold the attributes, and the methods are just C-functions built around them. Let's define a structure "struct Fruit", that will hold attributes of a fruit like its color and its price:

#include <stdio.h>
#include <string.h>
#include <stlib.h>

struct Fruit {
    char *name;
    char *color;
    int cent;
};

int main(void) {
    struct Fruit apple;
    apple.name  = malloc(100);
    apple.color = malloc(100);
    strcpy(apple.name, "Apple");
    strcpy(apple.color, "green");
    apple.cent = 25;
    puts(apple.color);
    printf("%d\n", apple.cent);
    free(apple.name);
    free(apple.color);
}

As you can see, the members of structures are accessed using the "."-operator.

Notice, that the datatype of the structure is "struct Fruit". Not "struct", not "Fruit", but "struct Fruit".
The variable "apple" is of type "struct Fruit".


17. Pointers to Structures

It is also possible to point pointers to structure-"objects".
Members are then accessed using the "->"-operator (which is a shortcut for "(*pointer).member").
Instead of a fruit, let's model a lamp, using a structure of type "struct Lamp" with three elements:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct Lamp {
    char *name;
    char *state;
    int lightintensity;
};

int main(void) {
    struct Lamp *lamp1    = malloc(sizeof(struct Lamp));
    lamp1->name           = malloc(20);
    lamp1->state          = malloc(10);
    lamp1->lightintensity = 50;
    strcpy(lamp1->name, "First Lamp");
    strcpy(lamp1->state, "off");
    puts(lamp1->name);
    puts(lamp1->state);
    printf("%d\n", lamp1->lightintensity);
    free(lamp1->name);
    free(lamp1->state);
    free(lamp1);
}


18. Emulating Object-Oriented Programming in C

Referring to the example in the last chapter ("Pointers to Structures"), let's add useful functions (as "methods") around the structure, which is controlled with the pointer pointing to it. The first argument to these structure-related functions is always the pointer to the structure (similar to other object-oriented languages by the way: There's for example the "self" in Python, and the "this" in C++).
And also, let's use some comments and whitespace, to make the combination of the structure and its functions look more like a class:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* class Lamp: */

    struct Lamp {
        char *name;
        char *state;
        int lightintensity;
    };

    struct Lamp *Lamp_new(char *name, int lightintensity, char *state) {
        struct Lamp *self = malloc(sizeof(struct Lamp));
        self->name        = malloc(20);
        self->state       = malloc(10);
        strcpy(self->name, name);
        strcpy(self->state, state);
        self->lightintensity = lightintensity;
        return self;
    }

    void Lamp_switchOn(struct Lamp *self) {
        strcpy(self->state, "on");
        printf("'%s' is on at %d Watt.\n", self->name, self->lightintensity);
    }

    void Lamp_switchOff(struct Lamp *self) {
        strcpy(self->state, "off");
        printf("'%s' is off.\n", self->name);
    }

    void Lamp_newLightBulb(struct Lamp *self, int light) {
        if (strcmp(self->state, "on") == 0) {
            puts("Light bulb can not be changed.");
            printf("First, '%s' has to be switched off.\n", self->name);
        } else {
            self->lightintensity = light;
            printf("Light bulb in '%s' has been changed.\n", self->name);
            printf("The new bulb has %d Watt.\n", self->lightintensity);
            Lamp_switchOn(self);
        }
    }

    void Lamp_destruct(struct Lamp *self) {
        free(self->name);
        free(self->state);
        free(self);
    }

/* End of class Lamp. */


int main(void) {
    struct Lamp *lamp1 = Lamp_new("First Lamp", 50, "off");
    struct Lamp *lamp2 = Lamp_new("Second Lamp", 40, "off");
    Lamp_switchOn(lamp1);
    Lamp_switchOn(lamp2);
    Lamp_newLightBulb(lamp2, 100);
    Lamp_switchOff(lamp2);
    Lamp_newLightBulb(lamp2, 100);
    Lamp_destruct(lamp1);
    Lamp_destruct(lamp2);
    return 0;
}

Now that's useful, isn't it?


19. Unions

Unions are declared in the same way as structures, just the keyword "union" has to be used instead of "struct". The difference between the two has to do with memory allocation: For a structure, the sum of the memory size of all its members is allocated. For a union, memory only of the size of its largest member is allocated.
So you can save up some memory, when you use an union instead of a structure. The price for that is, that only one member of the union can be accessed at a time. I don't use unions, but if you want to try it, you can read here, how it is done.


20. Arrays

As you can see, in my text it took me a long time to get to arrays.
They aren't very flexible. Their size has to be declared, when declaring the array. Then they can store several units of data of the same type. Their memory can't be reallocated to a different size afterwards. When arrays are declared inside a function, their memory is automatically destroyed, when the function ends. Therefore they can't be returned from a function. Not even by pointing a pointer to them, because the pointer will basically point to nowhere, if the reservation of the memory, it points to, is lifted.
So in a way arrays are a simpler way to statically reserve memory and to store data there. But as you know by now, how you can do that with a pointer in combination with "malloc()" yourself, you don't really need arrays. And using pointers, you don't have to deal with the drawbacks of arrays. Anyway, here are some arrays:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    int n[5] = {10, 11, 12, 13, 14};
    double f[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
    /* Also possible: char c[5] = "abcde" */
    char c[5] = {'a', 'b', 'c', 'd', 'e'};
    int i;
    for (i=0; i<5; i++) {
        printf("%d \t %.1f \t %c \n", n[i], f[i], c[i]);
    }
    /* String: */
    /* Or: char d[20] = "Hello" */
    char d[20];
    strcpy(d, "Hello");
    printf("%s\n", d);
    return 0;
}

When you combine declaration and definition of the array (like above), you can leave out the number of bytes in the declaration ("int n[] = ...";).

Of course you can (and probably should) reserve more memory for the array than is really needed. You just have to watch out, that you don't define more elements, than the array can carry, as that would lead to a severe bug.

When you just declare an array, simply nothing is written yet into the reserved memory. Whatever the bytes in the reserved area already hold, is still there, until you write something else into the array.
That means, if you declare an array of let's say 100 bytes, and you want to fill it with some numbers, there isn't any built-in way to tell, how many numbers the array already holds at the moment, and how many of the bytes are still uninitialized. You just have to keep track, of how many numbers you have already written, yourself:

#include <stdio.h>

int main(void) {
    int a[100];
    int anum = 0;
    a[0] = 10;
    anum++;
    a[1] = 20;
    anum++;
    a[2] = 25;
    anum++;
    printf("%d elements in the array.\n", anum);
    a[2] = 0;
    anum--;
    printf("%d elements in the array.\n", anum);
    return 0;
}

While some compilers allow to declare the size of an array with a variable (like "int arr[b];", others require constant values (like literal numbers) for that. It's a good idea to define constants (as compiler macros) for that, if possible:

#define ARR_SIZE 5

void main(void) {
    char arr[ARR_SIZE];
}

The name of an array can be interpreted as a pointer to the first element of the array.
"printf("%s\n", arr);" (like in the first example above) makes use of that for example. But "pointer arithmetic" (see above) isn't possible with the names of arrays though.


21. Memory Areas of C-programs

Let's take a look at the memory-layout of a C-program. When a C-program is started, it builds up the following memory-segments in cooperation with the operating-system:

The shell-command "size" displays the sizes of the text- and data-segments.
The shell-command "nm" displays more information about memory-usage of variables and other symbols.

Pointers inside functions are usually stored on the stack, but the memory they point to is usually on the heap.

The string literal of

/* Not a valid string initialization! Use malloc(), then strcpy() instead: */
char *a = "Hi";

is stored in the read-only-section of the initialized data segment or even in the (read-only) text-segment.
The string literal of

char a[] = "Hi";

is short for

char a[] = {'H', 'i', '\0'};

and therefore stored on the stack.


22. Linked Lists (Dynamically Changeable Lists)

In languages like Perl or Python, there is the datatype "list". It can just hold different kinds of data, like integers or strings. And these lists can easily be changed lateron, for example extended or reduced at runtime. Consider this fundamental Python code:

#!/usr/bin/python3
# coding: utf-8

a = []
a.append(3)
a.append(6)
a.append("Hello")
print(len(a))
print(a)

a.pop()
print(len(a))
print(a)

The C-language itself doesn't provide such "linked lists" with these features. However, on Linux-systems, there's an implementation, that can be included with "#include <sys/queue.h>". Here would be examples, on how to use that library. Not so easy, it seems.

In the first terms of university-level programming classes, it is a common exercise to write such an implementation of a linked list. My approach can be found on my GitHub-page (doubly linked list). But that code is just experimental, don't use it for productivity.


23. Getting a Random Number

Here's an example of getting a random number in the range of 0 to 9.
There are several ways in C to achieve this. This is just a basic one for average use. If you need higher level randomness, you'll have to look for more sophisticated ways (see discussions like
this one).

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    int i;
    int r;
    /* srand() seeds the random generator. Only use it once:  */
    srand(time(NULL));
    for (i = 0; i < 20; i++) {
        r = rand() % 10;
        printf("%d\n", r);
    }
    return 0;
}

On Linux, there are also the functions "random()" and "srandom()". Not sure, what their advantage is. The example above works also on Linux.


24. Alternative Syntaxes

The "?"-operator can be used to write certain conditions in a different way. You may see that syntax in other people's code. I wouldn't use it, I'd write the condition in the ordinary way, even if it's longer:

#include <stdio.h>

int main(void) {
    int a = 2;
    int b = 10;
    int max;

    /* Ordinary syntax: */
    if (a > b) {
        max = a;
    } else {
        max = b;
    }
    printf("%d\n", max);

    /* With ? : */
    max = (a > b) ? a : b;

    printf("%d\n", max);
}


25. Working with Files

This is how you read from a text-file (called "test"):

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    char fname[] = "test";
    FILE *fp = fopen(fname, "r");
    if (fp == NULL) {
        printf("\nCouldn't read file \"%s\". Aborting.\n\n", fname);
        exit(1);
    }
    int c;
    while ((c = getc(fp)) != EOF) {
        putc(c, stdout);
    }
    fclose(fp);
    return 0;
}

"EOF" and "stdout" are defined in "stdio.h". "EOF" means "end of file" (it's just an integer, of "-1" in my case).
"stdout" is the program's standard output-channel. When you send data to stdout, it is usually printed on the screen (in a terminal).
"getc()" reads in one character.
"FILE" is a typedef'ed name for a structure defined in "stdio.h". "fp" is a pointer that points to such a structure. "fopen()" returns such a pointer.

And this is how you write to a text-file (called "test2"):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void) {
    char schiller[] = "Und blicket sie lange verwundert an.\n"
                      "Drauf spricht er: \"Es ist euch gelungen,\n"
                      "Ihr habt das Herz mir bezwungen,\n"
                      "Und die Treue, sie ist doch kein leerer Wahn ...\"\n";
    char fname[] = "test2";
    if (access(fname, F_OK) == 0) {
        printf("\nFile \"%s\" already exists. Aborting.\n\n", fname);
        exit(1);
    }
    FILE *fp = fopen(fname, "w");
    if (fp == NULL) {
        printf("\nCouldn't open file \"%s\" for writing. Aborting.\n\n", fname);
        exit(2);
    }
    int i;
    for (i=0; i < strlen(schiller); i++) {
        putc(schiller[i], fp);
    }
    fclose(fp);
    return 0;
}


26. Keyword Summary of Variable Datatypes

Variable Declarations:

Elementary datatypes:

A variable of each of these datatypes (except void) can be declared as:

Special Declarations:



Back to the first page about C
Back to the computing-page


Author: hlubenow2 {at-symbol} gmx.net