Table of Contents
c99 (Analysis of some aspects of the c99 standard)
Compilation
In old gcc
compilers, if you try to compile a program written with the c99 standard, a compilation warning is notified by the compiler. You must use the specific -std
option of the gcc
compiler, to explicitly specify the standard used in the program you want to compile:
gcc -Wall --pedantic -std=c99 file.c
For new gcc
compilers the c99 standard is the default. As a consequence, to know if a program is adherent with the old c90
standard, it must be compiled with the following options:
gcc -Wall --pedantic -std=c90 file.c
ISO/IEC 9899:1999 http://www.iso.org/iso/catalogue_detail.htm?csnumber=29237
Table which highlights supported features in GCC compilers https://gcc.gnu.org/c99status.html
The C standards
In the following table the C standards are listed. The compiler predefines the following macros, that can be used to check if a given standard is supported and possibly, in the case the standard is not supported, to check if the compiler provides a C90 implementation.
Standard | NAME | Predefined macro |
---|---|---|
ANSI X3.159-1989 | C89 | __STDC__ |
ISO/IEC 9899:1990 | C90 | __STDC__ |
ISO/IEC 9899-1:1994 | C94 | __STDC_VERSION__ = 199409L |
ISO/IEC 9899:1999 | C99 | __STDC_VERSION__ = 199901L |
ISO/IEC 9899:2011 | C11 | __STDC_VERSION__ = 201112L |
Compiling with:
gcc -std=c99 c99_macro_std_version.c
the following program:
- c99_macro_std_version.c
/* c99: example of macro useful to identify the standard used by the compiler */ #include <stdio.h> int main() { printf("__STDC__: %d __STDC_VERSION__: %ld\n", __STDC__, __STDC_VERSION__); return 0; }
the output is:
__STDC__: 1 __STDC_VERSION__: 199901
Some new features included in the standard
Inline functions
Function is inline inserted in the code of the program.
Usually it is better to avoid the use of inline
functions because:
- gcc is better than you in code optimization
- the generated code is bigger
- you cannot use function pointers
- functions evolve and may no longer be suitable to be declared as
inline
- compilation time increases
For more details: http://www.greenend.org.uk/rjk/tech/inline.html
Intermingled declarations and code
Variables can be declared in any point of the source code. In the previous c90
version, the declaration of the variables was restricted to the start of a block.
This possibility is very useful for three main reasons:
- Variable used in the
for
loops can be initialized directly inside the loop with scope restricted to the loop:
- c99_var1.c
/* c99: example of a variable declared inside a for loop */ #include <stdio.h> int main() { int i = 123; for (int i=0; i<3; i++) printf("%d ", i);; printf("\nAFTER THE FOR: %d\n", i); return 0; }
The above code corresponds to the following code written in the c90
standard:
- c90_var1.c
/* c90: how to restrict the scope of the variable used to manage the cycle in a for loop */ #include <stdio.h> int main() { int i = 123; { int i; for (i=0; i<3; i++) printf("%d ", i); } printf("\nAFTER THE FOR: %d\n", i); return 0; }
- Allowing the declaration of variable-length arrays (see in this page in the following)
- Declare the variables exactly where they are needed (limiting the scope of variables is useful, because if a variable exists for a smaller time, the probability of bugs is reduced. As a consequence, a good programming style is to use blocks inside blocks and declare a variable only when it is needed).
- c99_scope.c
/* c99: limiting the scope of variables declaring them when needed or declaring them in a block inside a block */ #include <stdio.h> int main() { int x; x = 2; /* i cannot be used here */ int i; i = x; /* Block inside a block */ { /* This is another variable, it is not i=x */ int i; i = 3; /* At the end of the block i=3 no longer exist */ } /* i=x still exist */ return 0; }
Variable-length arrays
Variable-length arrays (VLAs) are classical C arrays, but with a length not declared as a constant expression.
Drawbacks of VLAs are two:
- They are declared as any automatic variable in the stack, which has a limited memory space. As a consequence, the use of VLAs is recommended only when their size is relatively small. As an example, VLAs could be useful for strings.
- The main drawback is that the implementation of VLAs is not mandatory in
c11
standard. Therefore, a program that makes use of VLAs could not be compiled by some “new” compilers.
A first easy example is the following, where an array with unknown dimension is read from stdin
- c99_variable-length_arrays_1.c
/* c99: VLAs, reading a vector with unknown dimension from stdin */ #include <stdio.h> int main() { int l; printf("Insert length: "); scanf("%d", &l); int v[l]; for(int i=0; i<l; i++) scanf("%d", &v[i]); for(int i=0; i<l-1; i++) printf("%d ", v[i]); printf("%d\n", v[l-1]); return 0; }
In this second example, VLAs are used in the function my_system
to concatenate two strings. The concatenation of the two strings is used as an argument of the system call system
.
- variable-length_arrays_2.c
/* c99: VLAs, system with the concatenation of two strings */ #include <stdio.h> #include <stdlib.h> #include <string.h> int my_system(char *s1, char *s2) { char s[strlen(s1) + 1 + strlen(s2) + 1]; strcpy(s, s1); strcat(s, " "); strcat(s, s2); return system(s); } int main() { my_system("ls", "-l"); return 0; }
Reference regarding malloc
vs. VLAs: http://www.drdobbs.com/the-new-cwhy-variable-length-arrays/184401444
A number of new data types
long double
Even if the long double
type was present from the very beginning in the first version of the C standard, the support in standard libraries of this type starts from the c99
version of the standard.
Constants of type long double
are identified by the L
or l
suffixes at the end of the number (e.g., 12.033134113425422423432L
).
The format specifier for printf
and scanf
functions family is “%Lf
” and “%LF
”, “%Lg
” and “%LG
” for a number with up to 6 digits of precision, and “%Le
” and “%LE
” for the hexadecimal format.
Usually in 32 bits architectures long double
are stored with an 80 bits precision, while in a 64 bit architecture they are usually stored with a precision of 128 bits (see https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format). The fact that dimension and characteristics of this type change depending on the architecture and on the compiler leads to compatibility problems when binary files containing long double
values are transferred between different PCs.
In the following program some statistics regarding the precision of long double
are inspected and an example regarding the printf
function is provided:
- c99_long_double.c
/* c99: some statistics regarding long double and example of the use of the printf function */ #include <stdio.h> #include <float.h> int main() { long double ld = 1342344.42423412442344141232412L; printf("LEN:\n"); printf(" Float len: %zu\n", sizeof(float)); printf(" Double len: %zu\n", sizeof(double)); printf("Long double len: %zu\n", sizeof(long double)); printf("\nNUMBER OF SIGNIFICANT DIGITS:\n"); printf(" Float significant digits = %d\n", FLT_DIG); printf(" Double significant digits = %d\n", DBL_DIG); printf("Long double significant digits = %d\n", LDBL_DIG); printf("\nPRINTF:\n"); printf("%Lf\n", ld); printf("%Lg\n", ld); printf("%Le\n", ld); return 0; }
Output:
LEN: Float len: 4 Double len: 8 Long double len: 16 NUMBER OF SIGNIFICANT DIGITS: Float significant digits = 6 Double significant digits = 15 Long double significant digits = 18 PRINTF: 1342344.424234 1.34234e+06 1.342344e+06
A good reference regarding float.h
library and floating points numbers in general: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/float.h.html
long long int
This new type and the relative unsigned
version has been added to the allowed standard C types.
In particular:
long long, long long int, signed long long, signed long long int''
- They are equivalent ways to identify
signed long long
integers. The standard guarantees that in a variable of such type can be stored a number at least in the range [−9223372036854775807, +9223372036854775807], and the minimum size of such a kind of data is 64 bits.
- Constants are identified by the suffixes
ll
orLL
added at the end of the integer constant. Examples are 7ll, 7LL, 07LL, 0x7ll.
- For
printf
andscanf
functions the%lld
format specifier is typically used. Other format specifiers are possible, for example for octals or exadecimals numbers (e.g.,%llo
,%llx
,%llX
).
unsigned long long, unsigned long long int
- They are the same as
long long
(i.e., dimension of at least 64 bits), but only for unsigned positive integer numbers. As a consequence, the minimum range of number that can be stored in this type of variables is [0, +18446744073709551615].
- Constants are identified by the suffixes
U
oru
concatenated with the suffixesll
orLL
added at the end of the integer constant. Examples are 7Ull, 7LLu, 0x7llu.
- For
printf
andscanf
functions the%llu
format specifier is typically used. Other format specifiers are possible, for example for octals or exadecimals numbers (e.g.,%llo
,%llx
,%llX
).
The <limits.h>
library defines the limits for long long int
type:
long long
in the range [LLONG_MIN,LLONG_MAX]unsigned long long
in the range [0,ULLONG_MAX]
- c99_long_long_int.c
/* c99: the long long int type */ #include <stdio.h> #include <limits.h> int main() { long long int a; unsigned long long int b; printf("Range long long: [%lld, %lld]\n", LLONG_MIN, LLONG_MAX); printf("Range unisigned long long: [0, %llu]\n", ULLONG_MAX); a = -2; b = 2; printf("a: %lld b: %llu\n", a, b); printf("%3lld is hex %llX\n", a, a); printf("SIZE: %zu\n", sizeof(long long int)); return 0; }
Output:
Range long long: [-9223372036854775808, 9223372036854775807] Range unisigned long long: [0, 18446744073709551615] a: -2 b: 2 -2 is hex FFFFFFFFFFFFFFFE SIZE: 8
More details: http://www.drdobbs.com/the-new-c-integers-in-c99-part-1/184401323
boolean (header: stdbool.h)
The new _Bool
(or bool
defined as macro) type:
- guarantees the possibility of comparison between boolean values (before
_Bool
atrue
boolean value in C was a number other than 0, i.e., -15, 1, 123) - it occupies in memory 1 byte, instead the small integer type (
short int
) occupies at least 2 bytes.
By including the header stdbool.h
the constants true
and false
are defined.
Casting: (bool)0.5
evaluates to true
, whereas (int)0.5
evaluates to 0
(false
).
The language guarantees that any two true values will compare equal (which was impossible to achieve before the introduction of the type)
- c99_bool_type.c
/* c99: the _Bool type */ #include <stdio.h> #include <stdbool.h> /* Header for _Bool */ int main() { _Bool x; bool y; /* bool is a macro with the same meaning of _Bool */ x = true; y = true; if (x==y) printf("Equals\n"); printf("VALUES-> true: %d false: %d\n", true, false); printf("SIZES -> _Bool: %ld short: %ld\n", sizeof(_Bool), sizeof(short)); return 0; }
Output:
Equals VALUES-> true: 1 false: 0 SIZES -> _Bool: 1 short: 2
complex (header: complex.h)
The c99
standard defines full support to complex numbers through the new complex
type defined in complex.h
. The three types of complex numbers that can be defined are listed in the following in increasing size order: float complex
, double complex
and long double complex
.
I
is a constant to identify an imaginary number. Functions creal()
and cimag()
have been defined to access the real or the imaginary part of a complex number, respectively. All math functions (e.g., sin()
, cos()
, sqrt()
) have an counterpart to be used with complex numbers. Such functions are the same defined in math.h
but their name starts with the suffix c
(e.g., csin()
, ccos()
, csqrt()
).
The following example should clarify the use of complex numbers:
- c99_complex.c
/* c99: example regarding complex numbers */ #include <stdio.h> #include <complex.h> #include <tgmath.h> int main(void) { /* Square 1 */ double complex z1 = I * I; printf("I * I = %.1f+%.1fi\n", creal(z1), cimag(z1)); /* Square 2 */ double complex z2 = pow(I, 2); // imaginary unit squared printf("pow(I, 2) = %.1f+%.1fi\n", creal(z2), cimag(z2)); /* Conjugates numbers */ double complex z4 = 1+2*I, z5 = 1-2*I; // conjugates printf("(1+2i)*(1-2i) = %.1f+%.1fi\n", creal(z4*z5), cimag(z4*z5)); /* Square root */ double complex z6=csqrt(-1); printf("sqrt(-1) = %.1f+%.1fi\n", creal(z6), cimag(z6)); }
Output:
I * I = -1.0+0.0i pow(I, 2) = -1.0+0.0i (1+2i)*(1-2i) = 5.0+0.0i sqrt(-1) = 0.0+1.0i
The specific header tgmath.h
defines a type-generic macro for each mathematical function defined in both math.h
and complex.h
. Basically, for each math function there are 6 variants: 3 for types float
, double
and long double
, and 3 for types float complex
, double complex
and long double complex
.
A good reference can be downloaded here: http://en.cppreference.com/w/c/numeric/complex
Other features
Other characteristics added by the c99
standard and not discussed here are:
- The use of C++ style comments:
//
- Flexible array member: https://en.wikipedia.org/wiki/Flexible_array_member
- Library
tgmath.h
with defines function forfloat
,double
,long double
,complex float
,complex double
andcomplex long double
(only some hints are provided in this guide) - The
snprintf
(which is a safe version of thesprintf
function) and other new functions - Others features introduced by the
c99
standard can be found in https://en.wikipedia.org/wiki/C99 or in the standard
If you found any error, or if you want to partecipate to the editing of this wiki, please contact: admin [at] skenz.it
You can reuse, distribute or modify the content of this page, but you must cite in any document (or webpage) this url: https://www.skenz.it/cs/c_language/c99?do=