User Tools

Site Tools


cs:c_language:c99

logo skenz.it

Return to Home page

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:

  1. gcc is better than you in code optimization
  2. the generated code is bigger
  3. you cannot use function pointers
  4. functions evolve and may no longer be suitable to be declared as inline
  5. 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 or LL added at the end of the integer constant. Examples are 7ll, 7LL, 07LL, 0x7ll.
  • For printf and scanf 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 or u concatenated with the suffixes ll or LL added at the end of the integer constant. Examples are 7Ull, 7LLu, 0x7llu.
  • For printf and scanf 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:

  1. guarantees the possibility of comparison between boolean values (before _Bool a true boolean value in C was a number other than 0, i.e., -15, 1, 123)
  2. 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: //
  • Library tgmath.h with defines function for float, double, long double, complex float, complex double and complex long double (only some hints are provided in this guide)
  • The snprintf (which is a safe version of the sprintf 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
/web/htdocs/www.skenz.it/home/data/pages/cs/c_language/c99.txt · Last modified: 2017/11/21 14:03 (external edit)

Privacy Policy