Features of C99

Introduction

C99 is the 1999 standard of the C programming language. C is a simple, low level language, that is best suited for systems programming.

This article will present a number of C99's features. Some of these features have yet to appear in C++, and therefore might not be familiar to some C++ programmers.

We will start off easy, with minor backports from C++, then move up to C99-only features, to wrap it up with "serious" code, adapted for this article from a small, real life project.

The source code in this article was tested to compile with Pelles C IDE 7, however due to the popularity and age of C99, the code should build fine with many other C compilers. Just be sure to enable C99 support, if needed.

No mandatory return for main()

As in C++, if the return statement is omitted in the  main()  function, a  return 0;  is implied.

Booleans

The  _Bool  data type is introduced, which behaves like an unsigned integer capable of storing only 1 or 0.

The supporting header  stdbool.h  contains the macros  bool true  and  false  expanding to  _Bool , 1 and 0 respectively.

Example:  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    bool b = false;

    printf("%u\n", b);

    b = 5 > 3;
    printf("%u\n", b);

    b = 0;
    printf("%u\n", b);

    b = -987;
    printf("%u\n", b);
}


Output:  
0
1
0
1

%zu for size_t

The  %zu  format specifier was introduced specifically for  size_t , so as to clear the confusion of having to choose in between the unsigned integer specifiers  %u %lu , and more recently  %llu .

Example:  
1
2
3
4
5
6
7
8
9
10
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

int main(void)
{
    size_t sz = SIZE_MAX;

    printf("%zu\n", sz);
}


Possible output:  
4294967295

Functions know their own name

The  __func__  identifier behaves like a constant  char  array containing the name of the function where it is invisibly declared.

Example:  
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

void i_know_my_name(void)
{
    printf("%s\n", __func__);
}

int main(void)
{
    i_know_my_name();
    printf("%s\n", __func__);
}


Output:  
i_know_my_name
main

Variable-length arrays

The variable-length arrays (or VLA's) are arrays that can be declared by using a variable, instead of a compile-time constant, for their size. They do not have variable length as in being able to resize.

VLA's are infamous because they're allocated on the stack and not the heap. The stack area is used for local variables, and is more limited in size than the heap. If the size of the VLA is too big, a stack overflow will occur, resulting in a crash.

Still, the VLA is a very useful tool when the programmer wants to use small arrays, while avoiding the tedious malloc()  +  free()  business.

Example:  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// This program will construct and display an n*n identity matrix.

#include <stddef.h>
#include <stdio.h>

int main(void)
{
    size_t n=0;

    printf("Please input `n': ");
    scanf("%zu", &n);

    int matrix[n][n];

    for (size_t i=0; i < n; ++i)
        for (size_t j=0; j < n; ++j)
            if (i == j)
                matrix[i][j] = 1;
            else
                matrix[i][j] = 0;

    for (size_t i=0; i < n; ++i)
    {
        for (size_t j=0; j < n; ++j)
            printf("%d ", matrix[i][j]);

        printf("\n");
    }
}


Sample output:  
Please input `n': 10
1 0 0 0 0 0 0 0 0 0 
0 1 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 0 0 
0 0 0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 1 0 
0 0 0 0 0 0 0 0 0 1 

Variadic macros

Functions can accept a variable number of parameters by using the ellipsis ( ... ). Starting from C99, so too can macros. In the macro's definition,  __VA_ARGS__  will be used to expand the parameters.

Example:  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TIME_PRINTF(format, ...) do { \ time_t t = time(NULL); \ const char *prefix = "%s -> "; \ char time_format_vla[strlen(prefix) + strlen(format) + 1]; \ strcpy(time_format_vla, prefix); \ strcat(time_format_vla, format); \ printf(time_format_vla, ctime(&t), __VA_ARGS__); \ } while (false)

int main(void)
{
    srand(time(NULL));
    TIME_PRINTF("Hello %s, your number is %d! Please wait...\n\n", "User", rand() % 100);

    // waste some time
    for (size_t n=0; n < SIZE_MAX; ++n);

    // unfortunately, we need to pass at least two parameters 
    TIME_PRINTF("%s", "So how's it going?");
}


Sample output:  
Wed Apr  3 12:33:23 2013
 -> Hello User, your number is 75! Please wait...

Wed Apr  3 12:33:33 2013
 -> So how's it going?

Designated initializers

C99 offers a way to control which member in a structure, or which element in an array, to initialize and to what value.

It's easier to just jump into the example for this one.

Example:  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>

int main(void)
{
    char ca[10] = {[4] = 'e', [0] = 'a', [2] = 'c', [1] = 'b', [3] = 'd', [9] = 'z'};

    // 0 1 2 3 4 . . . . . . 9
    // ca == {'a', 'b', 'c', 'd', 'e', 0, 0, 0, 0, 'z'}

    printf("Contents of ca:\n ");

    // the zeros are not printable, because they aren't the '0' character,
    // so we need to cast them to int so as to print their numeric value
    for (size_t i=0; i < sizeof ca; ++i)
        if (isprint(ca[i]))
            printf("%c ", ca[i]);
        else
            printf("%d ", (int)ca[i]);

    printf("\n\n");

    struct Test
    {
        char    c;
        int     i;
        float   f;
    };

    struct Test t = {.f = 3.14f, .c = 'Z', .i = 10};

    printf("Contents of t:\n c == %c\n i == %d\n f == %f\n", t.c, t.i, t.f);
}


Output:  
Contents of ca:
  a b c d e 0 0 0 0 z 

Contents of t:
  c == Z
  i == 10
  f == 3.140000

Compound literals

A compound literal is basically a nameless variables, and looks very similar to a cast. It works together beautifully with variadic macros and designated initializers to produce clean, high-level looking code.

In the simplest usage scenario, compound literals take the place of temporary variables, which we don't care to have around.

Example:  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

// this function will change the case of all letters in the message array,
// lowercase letters will become uppercase, and vice versa
void flip_case(char *message)
{
    printf("flip_case()\n");
    printf("Before: %s\n", message);

    for (size_t i=0, ml = strlen(message); i < ml; ++i)
    {
        const char temp = message[i];

        if (isupper(temp))
            message[i] = tolower(temp);
        else
        if (islower(temp))
            message[i] = toupper(temp);
    }

    printf("After: %s\n\n", message);
}

// this function will add 10 to an integer i
void add_ten(int *i)
{
    printf("add_ten()\n");
    printf("Before: %d\n", *i);
    *i += 10;
    printf("After: %d\n\n", *i);
}

// this function will add 1 to even numbers in the numbers array,
// only the first n numbers are operated on
void kill_evens(int *numbers, size_t n)
{
    printf("kill_evens()\n");
    printf("Before: ");

    for (size_t i=0; i < n; ++i)
        printf("%d ", numbers[i]);

    printf("\n");

    for (size_t i=0; i < n; ++i)
        if (numbers[i] % 2 == 0)
            numbers[i] += 1;

    printf("After: ");

    for (size_t i=0; i < n; ++i)
        printf("%d ", numbers[i]);

    printf("\n\n");
}

int main(void)
{
    flip_case((char[]){"Hello C99 World!"});

    add_ten(&(int){5});

    kill_evens((int[]){2, 3, 29, 90, 5, 6, 8, 0}, 8);

    printf("Current time: %s\n", ctime(&(time_t){time(NULL)}));
}


Output:  
flip_case()
Before:   Hello C99 World!
After:    hELLO c99 wORLD!

add_ten()
Before:   5
After:    15

kill_evens()
Before:   2 3 29 90 5 6 8 0 
After:    3 3 29 91 5 7 9 1 

Current time: Wed Apr  3 12:44:55 2013

For a more advanced example demonstrating the value of compound literals, consider this scenario: we've written our own  strscat()  function, which is basically a  strcat()  with an extra parameter for maximum length, and we want to test to see if it works correctly.

Now, I'll let the code talk.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <stddef.h>
#include <stdio.h>

///
/// @brief Appends contents of array `from` to array `to`.
/// @pre `limit` != `0`
/// @note No operation is performed for a `limit` of `0`.
/// @remarks Resulting array is NUL-terminated.
/// @param [out] to String to be written to.
/// @param limit Maximum number of bytes that string `to` can store, including NUL.
/// @param [in] from String to be copied from.
/// @returns Size of resulting string (NUL not counted).
///
size_t strscat(char *to, size_t limit, const char *from)
{
    size_t s=0;

    if (limit != 0)
    {
        while (to[s] != '\0')
            ++s;

        for (size_t i=0; from[i] != '\0' && s < limit - 1; ++i, ++s)
            to[s] = from[i];

        to[s] = '\0';
    }

    return s;
}

typedef struct
{
    char        *to;
    size_t      limit;
    const char  *from;
    const char  *result;
    size_t      retval;
} test_t;

static size_t tests_failed;

static void run_test(test_t *t)
{
    size_t i=0;

    if (t->retval != strscat(t->to, t->limit, t->from))
    {
        ++tests_failed;
        return;
    }

    while (t->result[i] != '\0' || t->to[i] != '\0')
        if (t->result[i] != t->to[i])
        {
            ++tests_failed;
            break;
        }
        else
            ++i;
}

#define RUN_TEST(...) run_test(&(test_t){__VA_ARGS__})

int main(void)
{
    RUN_TEST(
        .to     = (char[15]){"The Cutty"},
        .limit  = 15,
        .from   = " Sark is a ship dry-docked in London.",
        .result = "The Cutty Sark",
        .retval = 14
    );

    RUN_TEST(
        .to     = (char[15]){"The Cutty"},
        .limit  = 0,
        .from   = "this won't get appended",
        .result = "The Cutty",
        .retval = 0
    );

    RUN_TEST(
        .to     = (char[15]){"The Cutty"},
        .limit  = 15,
        .from   = "!",
        .result = "The Cutty!",
        .retval = 10
    );

    RUN_TEST(
        .to     = (char[]){"The Cutty Sark"},
        .limit  = 3,
        .from   = "this shouldn't get appended",
        .result = "The Cutty Sark",
        .retval = 14
    );

    RUN_TEST(
        .to     = (char[]){"The Cutty Sark"},
        .limit  = 1,
        .from   = "this shouldn't get appended, either",
        .result = "The Cutty Sark",
        .retval = 14
    );

    RUN_TEST(
        .to     = (char[]){""},
        .limit  = 1,
        .from   = "this had better not get appended!",
        .result = "",
        .retval = 0
    );

    (void)fprintf(stderr, "Number of tests failed: %zu.\n", tests_failed);
}


Ending notes

I hope you enjoyed reading this article, and as always, contact me via PM if you have suggestions for improving it.

你可能感兴趣的:(Features of C99)