The C Programming Language - Data-types, operators and expressions

Edusagar - notes - Data-types, operators and expressions
  • C has very few datatypes

    Data typeUsage
    charsingle byte
    floatsingle precision floating point
    doubledouble precision floating point
  • short and long applies to integers only and hence can be omitted from the following declarations:
    short int a;
    long int b;

  • Compiler is free to choose sizes for data-types subject to following conditions:
    short and int are atleast 16 bits long
    long is atleast 32 bit long
    short is not longer than int
    int is not longer than longsigned/unsigned applies to char and int both.

  • signed/unsigned applies to char and int both.

  • Enum Vs # Defines

    • values can be generated for enums.
    • enums can be printed in symbolic form by debuggers
    • enums offer the chance of compiler checks
  • by default constant 2.3 will be treated as double until it is specified as - 2.3f (float)

  • Arbitrary bit-sized patterns can be declared as:
    '\ooo'  (octal)
    '\xab' (hex)

  • String constants can be concatenated at compile time:

    #define mystr "hello," "world" 

    is equivalent to

    #define mystr1 "hello,world"

    use-case is in printf if you want to split a long string across multiple lines:


    is equivalent to:

  • Variables must be declared before their use.

  • non-automatic variables are initialized only once(before program starts executing) and the initializer has to be a constant. Automatic variables are initialized everytime the function or the block in which they are declared is hit. The initializer in this case can be any expression.

  • External and static variables are initialized to 0, while automatic variable has garbage value.

  • The result is implementation dependent if one tries to modify a const variable.

  • Leap year condition:
    if (((year%4 == 0) && (year%100 != 0)) || (year%400 == 0))

  • % operator cannot be applied to float and double

  • The direction of truncation for / and the sign of the result for % are machine-dependent for negative operands, as is the action taken on overflow or underflow.

  • expressions connected by logical operators - && or || are evaluated from left to right and evaluation stops as soon as the truth or falsehood of the result is known. e.g.

    if (ptr && ptr->value == val)

    ptr needs to be checked before de-referencing its value. So, if ptr is null, the evaluation stops there only and no de-referencing of pointer takes place.

    && has higher precedence over ||

  • automatic type conversion can convert a "narrow"(int) operand to a "wider"(float) one without losing information. However, the reverse operation loses information and may draw a compiler warning, but is not illegal.

  • float cannot be used as array index

  • Automatic type conversion:

    • If either operand is long double, convert the other to long double
    • Otherwise, if either operand is double, convert the other to double
    • Otherwise, if either operand is float, convert the other to float
    • Otherwise, convert char and short to int
    • Then, if either operand is long, convert the other to long.
  • Floats arent automatically converted to double. In general, mathematical functions like those in <math.h> use double precision. float is only used to save space or make code portable on machines on which double precision arithmetic is very expensive.

  • char is just a small integer, so can be freely used in arithmetic expressions.

  • atoi implementation:

    int atoi(char s[]) 
        int i, n = 0;
        for(i=0; s[i]>='0' && s[i] <= '9'; i++) {
            n = n*10 + (s[i] - '0');
        return n;
  • ctype.h provides methods for tests and conversion of characters e.g. tolower(), isdigit() etc.

  • definition of C gurantees that standard printable character set will never be negative, but arbitrary bit-sequence stored in character variable may be negative or positive depending on the machine. For portability, specify signed/unsigned in such cases.

  • Comparison between signed and unsigned operands is machine dependent because they depend on the size of various integer types.

  • If integer is 16 bits and long is 32 bits then -1L < 1U (unsigned integer gets promoted to signed long)
    -1L > 1UL  (both are long, -1L gets promoted to unsigned long)

  • If arguments are declared by a function prototype, it causes automatic coercion of any arguments when the function is called. In other words, there is no need to explicitly do a cast to double in the following case where int is passed as an argument:

    double sqrt(double);

    root = sqrt(2);

  • increment(++) and decrement(--) operators can only be applied to variables; expression like (i+j)++ is illegal.

  • /* remove all occurence of c from string s */
    void squeeze(char s[], int c)
    int i,j;

    for (i=0, j=0; s[i] != '\0'; i++) {
    if (s[i] != c) {
    s[j++] = s[i];

    s[j] = '\0';

  • void strcat(char s[], char t[])
    int i=0, j=0;

    while(s[i++] != '\0');

    while((s[i++] = t[j++]) != '\0');

  • Right shifting(>>) an unsigned quantity will fill the vacated bits with zero, for signed quantity it will fill with bit signs(arithmetic shift) or with 0(logical shift) - what actually happens is machine dependent.

  • /*getbits(x,p,n) returns right-adjusted n bits from x starting from p. e.g. getbits(x,4,3) returns 3 bits from position 4,3 and 2. */
    getbits(unsigned x, int p, int n)
    return ((x >> p-n+1) & ~(~0 << n));

  • use ~0 to get implementation independent all set of 1s. e.g. on 32 bit system it will return 32 SET bits, on 16 bits system it will 16 SET bits.

  • (n > 0) ? f : n

    if f is float and n is int, the above conditional expression will be of type float irrespective of whether n is positive or not. (int gets promoted to float if other operand is float in an expression)

  • Operator Precedence table:

    () [] -> .left to right
    ! ~ ++ -- +(unary) -(unary) * (type) sizeofright to left
    * / %left to right
    + -left to right
    << >>left to right
    < <= > >=left to right
    == !=left to right
    &left to right
    ^left to right
    |left to right
    &&left to right
    ||left to right
    ?:right to left
    = += -= *= /= %= &= ^= |= <<= >>=right to left
    ,left to right
  • C doesnt specify the order in which operands are evaluated (except &&, ||, ?:, ','). So, there is no gurantee which function will be evaluated first out of f() and g(). So, if one function depends on variables modified in other - it might not work as expected.
    x = f() + g()

  • There is no order of evaluation of expressions passed as function arguments. So, following is not correct:
    printf("%d %d\n", ++n, power(2,n));
    - there is no way to make sure ++n gets executed first and we pass the updated value in function power()

  • C standard leaves the handling of side-effects to the implementation of the compiler. e.g. the result of following is compiler and machine dependent:
    a[i] = i++;

  • Only thing the standard specifies is that all the side-effects on arguments take effect before a function is called.

comments powered by Disqus