Menu-Submenu

C type Declarations, Operators, Initialization




C type Declarations, Operators, Initialization



1) Reading declaration in C


Generally a declaration is composed of
   one variable name + exactly one "basic type" on far left of expression + zero or more "derived types"
   So "basic types" are augmented with "derived types"

1.1) Basic Types

   char           unsigned char         signed char
   short          unsigned short
   int             unsigned int
   long           unsigned long
   float           double            void
   struct tag    union tag             enum tag
   long long     unsigned long long    long double (ANSI/ISO C only)

1.2) Derived Types

1) *     pointer to...
           Pointer always "point to" something

2) []    array of...
           Arrays have to be "arrays of" something.
           It can be undimensioned [] or dimensioned [10], but the sizes don't really play significantly into reading a declaration (sizes are important to understand memory allocation of variable).

3) ()    function returning...
           It can be empty pair of parantheses () or with a prototype parameter list inside. Parameters lists (if present) don't really play into reading a declaration, and we typically ignore them.
           NOTE: parens used to represent "function returning" are different than those used for grouping (grouping parens surround the variable name, while "function returning" parens are always on the right).
           Functions are meaningless unless they return something not void.

A derived type always modifies something that follows, whether it be the basic type or another derived type.
To make a declaration read properly one must always include the preposition ("to", "of", "returning").

It's possible that a type expression may have no derived types (e.g., "int i" describes "i is an int"), or it can have many. Interpreting the derived types is usually the sticking point when reading a complex declaration, but this is resolved with operator precedence in the next section

1.3) Reading Declaration Rules

  1. Precedence rule
       Declarations are type expressions rather than computational ones, with
           "array of" [] and "function returning" () type operators have higher precedence than "pointer to" * (While use of "array of" [] and "function returning" () type operators together is invalid)

   2. Always start with the variable name, while reading declaration
       For example:    foo is ...

   3. Always end with the basic type:
       For example:    foo is ... int
       So all declaration start out this way "variable name is .... basictype"

   4. "go right when you can, go left when you must"  for filling in the middle.
           Using above mentioned precedence rule, consume derived-type token to the right as far as possible without bumping into a grouping parenthesis.
           Then go left to the matching parenthesis.

   5. (Subpart of Step 3) In presence of qualifier 'const',
              That keyword applies to whatever is immediately to its left;
              If there is nothing to its left, it applies to whatever is immediately to its right.
              For examples,
const char *ptr OR
char const *ptr
ptr is (non-const) pointer to a constant character
(can't change the value pointed by ptr, but you can change the pointer itself)
char *const ptr
ptr is constant pointer to non-const character
(can't change the pointer ptr, but can change the value pointed by ptr. Pointer always points to same address)
const char * const ptr OR
char const * const ptr
This is a constant pointer to constant character
(can neither change the value pointed by ptr nor the pointer ptr)
It is invalid to change the value of 'const' variable anytime except at the time of initialization.

1.4) Simple Declarations

   int     foo[5];  /* foo is an array of 5 ints */
   char    *foo;   /* foo is a pointer to char */
   double   foo(); /* foo is a function returning a double */

NOTE: Writing declaration description is done from left-to-right, while actually (memory) use is understood from right-to-left

1.5) Simple Example

   long **foo[7];
   Here variable name is touching two derived types: "array of 7" and "pointer to"

: long **foo [7];
   Start with the variable name and end with the basic type:
   foo is ... long

: long ** foo[7];
   Go right and consume the "array of 7"
   foo is array of 7 ... long

: long ** foo[7];
   Gone as far right as possible, so moving left side the innermost part is only touching the "pointer to"
   foo is array of 7 pointer to ... long

: long * *foo[7];
   The innermost part is now only touching a "pointer to"
   foo is array of 7 pointer to pointer to long

1.6) Complex Example

   char *(*(**foo [][8])())[];

: char *(*(**foo [][8])())[];
   foo is ... char

: char *(*(**foo[] [8])())[];
   Go right, as innermost part touches "array of" and "pointer to"
   foo is array of ... char

: char *(*(**foo[][8])())[];
   Go right, as innermost part still touches "array of" and "pointer to"
   foo is array of array of 8 ... char

: char *(*(** foo[][8])())[];
   Now hit parenthesis used for grouping. So backtrack to collect all the parts to the left (but only as far as the paren). This consumes the "pointer to"
   foo is array of array of 8 pointer to ... char

: char *(*(* *foo[][8])())[];
   Again backtrack to the left, so consume the next "pointer to":
   foo is array of array of 8 pointer to pointer to ... char

: char *(*(**foo[][8])())[];
   Now finished off the entire parenthesized subexpression, so "consume" the parens too. Go right, as the innermost part touching "function returning" on the right, and "pointer to" on the left:
   foo is array of array of 8 pointer to pointer to function returning ... char

: char *(* (**foo[][8])() )[];
   Again we hit grouping parenthesis, so backtrack to the left
   foo is array of array of 8 pointer to pointer to function returning pointer to ... char

: char * (*(**foo[][8])())[];
   Consuming the grouping parentheses, then go right as the innermost part is touching "array of" on the right, and "pointer to" on the left
   foo is array of array of 8 pointer to pointer to function returning pointer to array of ... char

: char * (*(**foo[][8])())[];
   Finally left with only "pointer to" on the left
   foo is array of array of 8 pointer to pointer to function returning pointer to array of pointer to char

NOTE: example won't compile unless it's initialized to provide the dimension of the innermost array, though none of this changes the fact that nobody would ever actually use this for anything

char *(*(**foo[][8])())[] = { 0 };    /* explicit initialization */
void myfunction(char *(*(**foo[][8])())[])    /* implicit init from function call */
{
   ...
}

1.7) Abstract Declarators


It is used when a type needs to be described but not associated with a variable name. These occur in two places : casts, and as arguments to sizeof

For example: int (*(*)())()

For such declarations, first thing is to "find where the variable name would go", then treat it like a normal declaration.
Generally there will be only one place where a variable name could possibly go.
This can be located using known syntax rules.
   1. to the right of all the "pointer to" derived type tokens
   2. to the left of all "array of" derived type tokens
   3. to the left of all "function returning" derived type tokens
   4. inside all the grouping parentheses

In example, rightmost "pointer to" sets one boundary, and the leftmost "function returning" sets another one.
   int (*(* x ) x ())()
Here x indicators show the only two places that could possibly hold the variable name, but the leftmost one is the only one that fits the "inside the grouping parens" rule.
So declaration as:
   int (*(*foo)())()

which is describe using 'Reading Declaration Rules' as:
   foo is a pointer to function returning pointer to function returning int

1.8) Semantic Restrictions/Notes

Not all combinations of derived types are allowed, and it's possible to create a declaration that perfectly follows the syntax rules but is nevertheless not legal in C (e.g., possible to create declaration syntactically valid but semantically invalid).

   1) Can't have arrays of functions
           Use "array of pointer to function returning..." instead.
   2) Functions can't return functions
           Use "function returning pointer to function returning..." instead.
   3) Functions can't return arrays
           Use "function returning pointer to array of..." instead.
   4) In multi-dimensional arrays, only the leftmost [] can be undimensioned
           Though multi-dimensional arrays (e.g., char foo[1][2][3][4]) are often suggested as poor data structuring. Nevertheless, when there is more than one array dimension, only the leftmost one is allowed to be empty.
For example: Valid    => char foo[]; char foo[][5];
                   Invalid => char foo[5][];
   5) "void" type is restricted
           Since "void" is a special pseudo-type, a variable with this basic type is only valid with a final derived type of "pointer to" or "function returning". It's invalid to have "array of void" or to declare a variable of just type "void" without any derived types.
           void *foo;          /* Valid */
           void foo();          /* Valid */
           void foo;           /* Invalid */
           void foo[];          /* Invalid */



2) Operator Precedence and Associativity


Operator
Operator Description
Associativity
()
[]
.
->
++    --
Parenthesis or    Function call
Brackets or array subscript
Dot or member selection
Arrow operator (for Member)
Postfix increment    & decrement
Left to Right
++    --
+    -
!    ~
(type)
*
&
sizeof
Prefix increment    & decrement
Unary plus    and minus
Not operator    and Bitwise complement
Type case
Indirection or dereference operator
Address of operator
Determine size in bytes
Right to Left
*    / %
Multiplication Division and Modulus
Left to Right
+    -
Addition and Subtraction
Left to Right
<<    >>
Bitwise Left shift and Right shift
Left to Right
<    <=
>    >=
Relational Less than and Less than equal to
Relational Greater than and Greater than equal to
Left to Right
==    !=
Relational Equal to and Not equal to
Left to Right
&
Bitwise AND
Left to Right
^
Bitwise Exclusive OR
Left to Right
|
Bitwise Inclusive OR
Left to Right
&&
Logical AND
Left to Right
||
Logical OR
Left to Right
?:
Ternary operator
Right to Left
=
+=    -=
*=    /=
%=    &=
^=    |=
<<=    >>=
Assignment operator
Addition and Subtraction assignment
Multiplication and Division assignment
Modulus and Bitwise assignment
Bitwise Exclusive and Inclusive OR assignment
Bitwise Left shift and Right shift assignment
Right to Left
,
Comma operator
Left to Right

2.1) Combining Indirection operator (*) and Increment/Decrement operator (++ and --)


The precedence of indirection operator (*) and increment/decrement operator (++ and --) are same and they associate from right to left

Examples:
1) x = *p++;
                   Postfix increment operator first (value of p is used in expression, then it will be incremented)
                   Value pointed by p will be dereferenced and then assigned to x
                   Value of p will be incremented for Postfix operator

2) x = ++*p
                   Dereference operator (*) will be first applied to p
                   Then Prefix increment (++) will be applied to *p and assigned to x

3) x = *++p
                   Prefix increment operator (++) is applied first to increment p
                   Then value at the new address is dereferenced and assigned to x.

3) Array


3.1) Array Initialization


array declared inside a function : elements of the array have garbage value
array is global or static : elements are automatically initialized to 0

Syntax for explicitly initialize elements of an array at the time of declaration
   datatype array_name[size] = { val1, val2, val3, ..... valN };

While initializing 1D array it is optional to specify the size of the array.
   For example: float temp[] = {12.3, 4.1, 3.8, 9.5, 4.5};    /* array of 5 floats */
                        int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};         /* array of 9 ints */

If the number of initializers is less than the specified size then the remaining elements of the array are assigned a value of 0.
   For example: float temp[5] = {12.3, 4.1};    /* Here temp[2] = [3] = [4] = 0 */

If the number of initializers is greater than the size of the array then, the compiler will report an error.

3.2) Passing the 1D Array to a Function


Like normal variables, an array variable also can be passed to a function; with the formal arguments of function declared as an array variable of same data type (matching array size or optional to specify size of array) .
Different ways:    void function_1(int arr[10]) { }    int a[10]; function_1(a);
                         void function_1(int arr[]) { }       int a[10]; function_1(a);
                         void function_1(int *p_arr) { }       int a[10]; function_1(a);
With respect to arrays, passed as an actual argument, the function gets access to original array and so any changes made to array inside function affects the original array. Changes made in formal arguments of array do affect the actual arguments.

3.3) Passing the 2D Array to a Function


Similar to 1D array, when a 2D array is passed to a function, the changes made by function effect the original array.
When a 2D array is passed to a function it is optional to specify the size of the left most dimensions.
Different ways:    void function_1(int arr[2][3]) { }       int a[2][3]; function_1(a);
                         void function_1(int arr[][3]) { }         int a[2][3]; function_1(a);
                         void function_1(int **pp_arr) { }       int a[2][3]; function_1(a);
                         void function_1(int (*p_arr)[3]) { }    int a[2][3]; function_1(a);
In all the cases, the type of the function formal argument is "a pointer to an array of 3 integers", only differ in representation way.

3.4) Array of Strings v/s Array of Pointers to Strings


In C, Strings = Array to char    i.e. char[] OR Pointer to char (array)   i.e. char*
also Pointers to Strings = Pointer to char (array)    i.e. char*


char arr_name[10] = "Hari";    OR
char arr_name[] = "Hari";
char *p_name = "Hari";

Array
Pointer variable (String literal)
sizeof()
10 bytes
4 bytes
Address
&arr_name = arr_name
&p_name <> p_name
Memory
"Hari" stored in stack
"Hari" stored in code section of memory (with Static storage duration, modifying them gives undefined behavior)
p_name stored in stack
Access
arr_name = "Ram";    /* invalid */
   As arr_name and string constant both are address
p_name = "Ram";    /* valid*/

arr_name[0] = 'b';    /* valid*/
p_name[0] = 'b';    /* invalid */
   As code section is Read-Only

arr_name++;    /* invalid */
p_name++;    /* valid */


Array of Strings
Array of Pointers to Strings
meaning
array of array of char
i.e. 2D array of char
array of pointer to char
Declaration
char var[x][y];
char *var[x];
Initialization
char names[3][15] = {
                                 "Hari",
                                 "ram",
                                 "shankar"
                               };
char *names[3] = {
                                 "Hari",
                                 "ram",
                                 "shankar"
                               };
OR

char *names[] = {
                                 "Hari",
                                 "ram",
                                 "shankar"
                               };
Memory
All characters are stored contiguous, with empty spaces filled with NULL character
Saves memory.

Not guaranteed that the all the strings will be stored in contiguous memory.
But the characters of a particular string literal are always contiguous.
Valid access
strcpy(names[1], "om"); /*valid*/
scanf(names[1], "om"); /*valid*/
names[1]= "om"; /* valid */
As it is 'pointer to char', so point to any string literal assigned to it.
Invalid access
names[1]= "om"; /* invalid */
As it is 'const pointer'
char *pets[3]; /*non-initialized*/

scanf("%s", pets[0]); /* invalid */
strcpy(pets[0], "cat"); /* invalid */
gets(pets[0]); /* invalid */
strcat(pets[0], "dog"); /* invalid */
As memory to store pointers of type 'char' is allocated.
But no allocation any memory for a string literal.
All the elements of 'pets' array contain garbage values (may be pointing to anywhere)



4) Structure & Union

4.1) Structure Initialization


To initialize a structure variable, it's members value must be placed in the same order and of the same type as defined in the structure template using curly braces.
   struct employee { char name[20];    int id; float sal; };
       struct employee emp1 = {"Alice", 1, 22.0};

All Structure members need not to be initialized. Initialization of member variables from last can be skipped, which uses Default Initialization Value.

Data Type
Integer
Float
Character
Default Initialization Value
0
0.0000
Blank

If the number of initializers is less than the number of members then the remaining members are given a Default Initialization Value.
   struct emp emp1 = {"Alice"};  is same as struct emp emp1 = {"Alice", 0, 0.0};
   struct emp emp2 = {};  is same as struct emp emp2 = {"", 0, 0.0};

But it is invalid to skip initialization member variables from start or at middle and cause compile time error.
   struct emp emp3 = {"Alice", , 22.0};    /* invalid */
   struct emp emp4 = {, , 22.0};             /* invalid */

4.2) Union Initialization


In case of Union, only initialize one of the members of the union at the time of declaration and this privilege goes to the first member.
For example:    union quantity { int count;   float weight; char label;  };
                      union quantity balls = {10};

With Designer initializer, it is possible to set the value of a member other than the first member of the union.
For example:    union quantity box = {.weight = 9.14 };
                      union quantity item = { .label = 'a' };

4.3) Initializing Array of Structure


Array elements are stored in consecutive memory Location. Like Array, Array of Structure can be initialized at compile time.

For general initialization of a structure variable, set of values are specified in curly braces in orderly manner.
   struct personBMI { int age; float wtKg; float heightFt; };
       struct personBMI emp1 = {34, 63.0, 5.8};
   OR
   struct personBMI { int age; float wtKg; float heightFt; } emp1= {34, 63.0, 5.8};

Similarly for initializing array of structure, each array element is initialized individually (using curly braces) and then all individual sets are combined (using comma) to form single set.
   struct personBMI { int age; float wtKg; float heightFt; };
       struct personBMI employees[3] = {
           {34, 63.0, 5.8},
           {27, 57.0, 5.6},
           {40, 65.0, 5.8} };
       struct personBMI person[ ] = {    /* without specifying array size */
           {34, 63.0, 5.8},
           {27, 57.0, 5.6},
           {40, 65.0, 5.8} };

General structure initialization rules also applicable for initializing array of structure.
       struct personBMI person[ ] = {
           {34, , },         /* valid */
           {},           /* valid */
           {40, 65.0, },      /* valid */
           {, 63.0, },        /* invalid */
           {, 57.0, 5.6},    /* invalid */
           {40, , 5.8} };    /* invalid */

4.3) Pointer Initialization within Structure


As string literal is special case; initialization of char pointer within structure (with string literal) varies with initialization of other data-type pointer within structure.
String literals are an exception. They already are somewhere in the memory and with initialization a pointer just point to that.

So straightforward initialization of char pointer within structure is as,
   typedef struct {
       int id;
       int sal;
       char *name;
   } employee;
   employee emp1 = { 1, 20, "Hari" };    /* valid */

But that is not the case with other data-type pointer in structure.
   typedef struct {
       int id;
       int sal;
       int *colleagues;
   } employee;
   employee emp1 = { 1, 20,  {3, 5, 6} }; /* invalid */

2 ways it can be achieved for any data type

4.3.1) With a temporary array variable

   typedef struct {
       int id;
       int sal;
       int *colleagues;
   } employee;
   int emp1_colleagues[3] = {3, 5, 6};    /* with array size */
   int emp1_colleagues[] = {3, 5, 6};      /* OR without array size */
   employee emp1 = { 1, 20,  emp1_colleagues };

4.3.2) C99 Compound Literals

   typedef struct {
       int id;
       int sal;
       int *colleagues;
   } employee;
   employee emp1 = { 1, 20,  (int[]) {3, 5, 6} };

4.4) Array of Structure Initialization containing other Structure-Pointer


Combining previous 2 topics, this can be achieved as below (using const pointers at places of some MACROs).

   struct ChildNode {
       const char* shape_name;
       const int sides;
   };
   struct ParentNode {
       const char* name;
       const int child_count;
       const struct ChildNode* child_params;
   };

   const struct ChildNode childAt5_AllSame[] = {{"Equilateral", 3}, {"Rhombus", 4}, {"Line", 2}, {"Square", 4}};
   const struct ChildNode childAt6_FewSame[] = {{"Isosceles", 3}, {"Rectangle", 4}, {"Isosceles Trapezoid", 4}};
   const struct ChildNode childAt8_NoneSame[] = {{"Scalene", 3}, {"Quadrilateral", 4}};
   const struct ParentNode Shapes[8+1] = {
       { "", 0, NULL},    /* 0 */
       { "", 0, NULL},    /* 1 */
       { "", 0, NULL},    /* 2 */
       { "", 0, NULL},    /* 3 */
       { "", 0, NULL},    /* 4 */
       { "Same sides", 5, childAt5_AllSame },   /* 5 */
       { "Few same", 3, childAt6_FewSame },   /* 6 */
       { "No side", 5, (struct ChildNode[]) {{"Point", 0}, {"Circle", 0}} },   /* 7 */
       { "None matching" , 2, childAt8_NoneSame }        /* 8 */
   };

5) NOTES


5.1) For any use of 'void' pointer (Dereferencing or Pointer arithmetic), it needs to be proper typecast of 'void' pointer first

5.2) Memory allocation initialization value
void *malloc(size_t size);
void *calloc(size_t n, size_t size);
void *realloc(void *ptr, size_t newsize);
Apart from the number & type of arguments for these functions, other difference is that,
   memory allocated by malloc() contains garbage value
   memory allocated by calloc() is always initialized to 0
   expanded memory by realloc() contains garbage value

5.3) 'typedef' with a pointer v/s with an array
Unlike 'typedef with a pointer', declaration of 'typedef with an array' differs
For example:    typedef int * iptr;    iptr a, *b; // same as int *a, **b;
                                                   iptr arr[10]; // same as int *arr[10];
           typedef int iarr[10];    iarr a, b, c[5]; // same as int a[10], b[10], c[10][5];