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:
%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:
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:
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.
Useful links
C99 articles
Software