Conversion and operations on the non arithmetic data types in C

 

What are the different C data types ?

The C standard groups its available types , in two large categories : the object types , and the function types .

The object types , in C are :

Integer types such as int  , short  , enum  …
Floating point number types such as float  , double , long double .
Structures
Unions
Arrays
Pointers
Atomic
void 

In C , the object types , also have many conceptual division . For example , the scalar category , is the category formed of the integers , such as int , and the floating point types , such as float , and the pointers .

A tutorial about the conversion between the arithmetic types , can be found here , this tutorial is about casting or the conversion , between the non arithmetic types .

What is the void type ?

The void type can be understood as the absence of a value .
For example , when declaring a function to have a void return type , this means that there is no value to be returned by this function . Also when stating that the function has , a not named , void parameter type , this means that this function has no parameters , as such it takes no arguments .

#include<stdio.h>

void helloWorld(void ){
    printf("hello world \n" );
    /*The helloWorld function , has no parameters , 
      as such it has one parameter of type void . 
      It returns no values , as such as its 
      return type , it also has the void type .*/}

int main(void ){
    helloWorld( );}
/*Output : 
hello world .*/

The void type , can also be used to disregard any value , returned by any expression . So an expression cast to the void type , has its value disregarded , or thrown away . A void expression , is evaluated , for its side effect .

(void) 1 ; 
/* The literal 1 is of the int type , 
   it is cast to the void type . The 
   result is an expression of the 
   void type , as a consequence , 
   the expression has no value .*/


void helloWorld(void ){
    printf("hello world \n" );}

helloWorld("Hello world" );
/*The expression , has a void type , 
  since the return type of the 
  helloWorld function is void . As
  a consequence , the expression has
  no value .  The  helloWorld function , 
  is used for printing the message 
  Hello world .*/

An expression of type void , cannot be cast , to any other type .

Pointer types

What are the different available pointer types ?

A pointer in C can be a pointer to an object type , or it can be a pointer to a function .

Which integer types are capable of storing pointers ?

A pointer holds an address . An address has a numeric value , and it has a type .

#include<stdio.h>

int main(void ){
    int val_i = 1 ; 
    /*Declare and initialize the 
      variable val_i .*/

    int *ptr_i = &val_i;
    /*ptr_i is a pointer to an int .
     It contains a numeric value , 
     which is the address of 
     val_i . This address is of type 
     int . So when dereferencing 
     this address , a specific number of 
     bits , equal to the number of bits 
     in the type int , is read .*/

    printf("@address : %p -> value : %d\n", ptr_i , *ptr_i ) ;
    /*Print the address stored in ptr_i ,
      and dereference the address , 
      to get the stored value .*/

/*Output :
@address : 0x7fff59e438ec -> value : 1 */}

The numeric value stored in a pointer is of an integer data type . The C standard specifies , that the integer data type stored in a pointer , can be stored in intptr_t and uintptr_t . Hence casting between pointers , and intptr_t , or uintptr_t , is always defined , by the C standard . Both intptr_t , and uintptr_t , are defined , in the stdint.h header .

For other integer data types , if the integer value of the pointer , is larger than the range , of the to convert to , integer data type , the result of the casting is not defined . And the inverse , which is if the integer data type value , has a number of bits larger than intptr_t or uintptr_t , the result of the conversion is not defined .

#include<stdio.h>
#include<stdint.h>

int main(void ){

  int val_i = 1 ; 
  /*Declare an int variable 
    val_i , and  initialize 
    it , with 1 .*/

  int *ptr_i = &val_i;
  /*Declare a pointer to an integer , 
    and initialize it with the address
    of val_i */

  uintptr_t t_uip = (uintptr_t) ptr_i;
  /*Cast a pointer to uintptr_t */

  printf("ptr_i : %p , t_uip : %#lx\n" , ptr_i , t_uip );
  /*Print the address stored in the pointer
    ptr_i , and the hexadecimal numeric value
    stored in t_uip .*/

  ptr_i	= (int * ) t_uip;
  /*Cast the integer value stored 
    in t_uip , to a pointer , to an 
    int ,  and assign the result
    to ptr_i .*/

  printf("ptr_i : %p , *ptr_i : %d\n" , ptr_i , *ptr_i );
  /*Print the address of ptr_i , and the 
      value stored by the address 
      referenced by ptr_i .*/
}

/*Output : 
ptr_i : 0x7fff599d88ec , t_uip : 0x7fff599d88ec
ptr_i : 0x7fff599d88ec , *ptr_i : 1 */

What is a pointer to void ?

A pointer to void , or the void pointer , can be understood as meaning , that for now , the type of the pointer , is not of interest . The absence of value in this case , is the type of the address , not the numeric value stored in the address . As stated earlier , a pointer variable stores address . An address has a numeric value , and a type .

#include<stdio.h>

int main(void ){
  void *ptr_v = (void *) 1 ;
  /*1 is an int literal , an integer
   can be cast to any pointer type .
   In this case , it is cast to 
   a pointer to void .
   The gotten address has a numeric value , 
   it has a void type , as such it does 
   not hava a type .*/
  printf("The stored address in ptr_v numeric value is : %p\n" , ptr_v );}

/*Output : 
The stored address in ptr_v numeric value is : 0x1 .*/

Any pointer type can be cast to a void pointer type , and any void pointer type , can be cast to any pointer type .

/*In this First example , any pointer sent to
  the printAddressVar function , is
  cast to a void pointer .
  The printAddressVar function , prints
  the numeric value of the address
  stored in the pointer .*/

#include<stdio.h>

void printAddressVar(void *var_vptr){
  printf("%p\n" , var_vptr );}

int main(void ){
  int flowers_i = 10 ;
  printAddressVar(&flowers_i );
  float angle_f = 3.4f;
  printAddressVar(&angle_f );
  double elevation_d = 1.3;
  printAddressVar(&elevation_d );}
/*Output : 
0x7fff5dc4f8e4
0x7fff5dc4f8e0
0x7fff5dc4f8d8 */


/*Second example .*/

#include<stdio.h>

int add (int x , int y ){return x+y; };
int sub (int x , int y ){return x-y; };
int mul (int x , int y ){return x*y; };
/*Declare , the add , sub , and multiply
  function .*/


typedef int (* fct_signature ) (int , int) ;
/*fct_signature is alias to :  pointer to a
  function that takes two int , and
  returns , an int .*/


int main(void ){
  void *arr_vptr[] = {add , sub , (void *) mul };
  /*add , sub , mul , are functions .
    A pointer to each , has a signature of
    int (* ) (int , int ) .
    Each pointer to each function is cast
    to the void pointer . 
    The gotten casts are stored in
    the array arr_vptr .*/

  int x = 1 , y = 2 , result = 0;

  result = ((fct_signature ) arr_vptr[0] )(x , y );
  /*Cast arr_vptr[0] to a pointer to a function .
    The pointer to the function has a signature :
    int (* ) (int , int ). After the casting ,
    the function is called .*/
  printf("%d\n" , result );
  /*Output : 3 */

  result = ((int (* ) (int , int )) arr_vptr[1] )(x , y );
  printf("%d\n" , result );
  /*Output : -1 */

  result = ((fct_signature ) arr_vptr[2] )(x , y );
  printf("%d\n" , result );
  /*Output : 2 */}

What is the null pointer ?

The null pointer constant , or shortly , the null pointer , is defined by the C standard , as having a value , the constant literal 0 .
A null pointer constant , is also defined by the C standard , as casting its constant literal value of 0 , to a pointer to the void type . The two definitions are equal , or the same .

#define NULL_PTR_CONSTANT 0

#define NULL_PTR_CONSTANT ((void* ) 0 )

The NULL macro , is defined in the standard header , stddef.h , as a null pointer constant .

A null pointer constant can be cast , to any other pointer type , the result is a null pointer , of that type . All null pointers compare equal .

#define NULL ((int * ) 0 )
/*Cast the null pointer 0 , to
  a pointer to an int , the 
  result is a null pointer .*/

#include<stdio.h>

int main(void ){
  printf("%d\n" , (int *) 0 == 0 ); 
  /*All null pointers are equal , printf 
    outputs 1 , which is true . 
    1 */}

A null pointer is not equal , to a not null pointer , so a null pointer is not equal to a pointer , which does not have the value 0 . A null pointer , can be used , in the initialization of pointer variables .

#include<stdio.h>

int main(void ){
    int var_i = 1 ; 

    int *var2_ip = &var_i;
    /*Initialize var2_ip with the address 
      of var_i .*/

    int *var3_ip = 0 ;
    /* The null pointer constant 0
       is cast , to (int * ) , the
       result is  a null pointer of the 
       int type , the null pointer 
       is stored in var3_ip .*/
    
    printf("%d\n" , var3_ip != var2_ip );
    /*A null pointer is not equal , to a 
      not null pointer , as such , printf
      outputs 1 , which is true .*/}

A null pointer constant can be understood , as itself , having a value of the constant literal 0 , the bit pattern used to represent a null pointer is not necessarily all 0 . A null pointer constant symbolizes , the absence of the numeric value , of the address , hence dereferencing a null pointer , is not defined . The address itself can be untyped , such as in the case , of a void null pointer , or typed , such as in the case , of an int null pointer .

Casting a null pointer constant , to an integer type , yields 0 , since the value of a null pointer , not the value of dereferencing a null pointer , is the constant literal 0 .

#include<stdio.h>

int main(void ){

  void *ptr_v = 0 ;
  printf("%d\n" , (int ) ptr_v );

  int *ptr_i = 0;
  printf("%d\n" , (int ) ptr_i ); }
/*Output :
0
0*/

Casting object type pointers

The key thing to remember , is that a pointer stores an address . An address has a numeric value , and since C is a typed language , an address has a type .
When dealing with casting between object type pointers , the numeric value stored in the address does not change , only the type of the address , is being reinterpreted .

This being said , object types pointers , can be cast between one another , the only requirement is that the object types have a common alignment . Alignment is where an object , can be placed in memory . If the object types , do not have a common alignment , the result of casting one pointer to another , is not defined .

/*Example 1 */

#include<stdio.h>


int main(void ){
  
  float var_f = 1.0f ;
  int var_i = (int ) var_f ; 
  /*Casting a float type to an int 
    type , will change its bit 
    representation .
    1.0f is encoded as :   
      00111111100000000000000000000000
    1 is encoded as :
      00000000000000000000000000000001*/

  printf("%f , %d\n" , var_f , var_i );
  /*Output : 
    1.000000 , 1 */


  float *ptr_f = &var_f ;
  int *ptr_i = (int *) ptr_f ;
  /*ptr_f is cast to a pointer 
      to an int . The address 
      stored in ptr_f is copied to 
      ptr_i . 
    The bit pattern stored at that 
      address did not change . It is 
      00111111100000000000000000000000 */
    printf("%f , %d\n" , *ptr_f , *ptr_i );
    /*Output : 
      1.000000 , 1065353216 */ }


/*Example 2 */

#include<stddef.h>
#include<stdio.h>

void toHex(unsigned char *ptr_uc , size_t size_data ){
    printf("%p : " , ptr_uc );
    /*Print the address of ptr_uc */
    for(size_t i = 0 ; i < size_data ; i++ )
	printf("%02x" , ptr_uc[i] );
        /*Print the hexadecimal representation 
          Of data .*/
    printf("\n" );}

struct flag{
  unsigned char num_stars ;
  unsigned int num_colors;
};

int main(void ){
  struct flag var_struct_flag =	{255 , 4294967295 };
  toHex((unsigned char *)&var_struct_flag , sizeof(struct flag ));
  /*Output : 
    0x7fff539528e8 : ff000000ffffffff , 
      address         data hex dump
    The structure is padded with 6 bytes , 
    this is why , there are six 0 between
    255 , and 4294967295 .*/

  struct flag var_struct_flag1 = {0 , 0x0000FFFF };
  toHex((unsigned char *)&var_struct_flag1 , sizeof(struct flag ));}
  /*Output : 
    0x7fff599b68e0 : 00000000ffff0000 
       adress         data hex dump
    This is a little Indian machine , 
    since the int type is stored in
    reverse .*/

As stated earlier , the void pointer can be cast to any other pointer , and vice versa , and the null constant pointer , can be cast to any other pointer .

Casting between qualified and unqualified pointer types

The C standard defines , that casting a pointer to an unqualified type , such as int * , to a pointer of the same type qualified , such as const int * , is always defined .

The available type qualifiers in C , are const , volatile , restrict and _Atomic.

#include<stdio.h>

int main(void ){
  int var_i = 1 ;
  int *ptr_i = &var_i ;
  const int *ptr_ci = ptr_i;
  /* *ptr_ci = 10;
     is illegal , because the
     pointer is a pointer to
     a const int. */
  *ptr_i = 10 ;
  /* Legal , because the pointer
     is a pointer to an int .*/}

Casting function type pointers

Pointers to functions , can be cast between one another . Also , as stated earlier , the void type can be cast to any other type , and any other type can be cast to the void type , and the null constant pointer , can be cast to any other pointer type .

If a pointer to a function , is cast , to another function , pointer type , and the target function type , is not compatible with the source function type , calling the function using the target type , is not defined .

int negate(int x ) {return -x ;}
int subtract(int x , int y ) {return x-y ;}

int main(void ){
  int (* sig1_ptr ) (int )  = negate;
  int (* sig2_ptr ) (int , int ) = subtract;
  sig2_ptr = (int (* ) (int , int ))  sig1_ptr;
    /* Any function pointer can be cast
       to any other function pointer .
       If the signature of the casted 
       function , is not compatible with the  
       signature of the pointer function type , 
       calling the function pointed by the 
       pointer , is not defined . Hence 
       sig2_ptr(1 , 1 ) is not defined .*/ }

Comparing pointers for order

The relational operators , less < , less or equal <= , larger > , larger or equal >= , can be used to compare for order .

Comparing pointers for order , is only defined for object type pointers , and it is only to be used , when comparing pointers to members of structures , arrays , and unions .

Pointers pointing to objects in a structure , declared after objects in the same structure , or to elements in an array , having a position higher , then elements in the same array , have a higher order.
The inverse is true , so pointers pointing to object in a structure , declared before objects in the same structure , or to elements in an array , having a position lower, then elements in the same array , have a lower order .
The last element comparable for order in an array , is equal to the array length plus one .

Pointers to any member of the same union , have the same order . Pointers to members of the same array , or to member of the same structure , having the same pointer address : type and numeric value , have also the same order .

#include<stdio.h>


struct rational {
  int numerator ;
  int denominator; };

union search{
  int close_i ;
  int far_i; };

int main(void ){

  struct rational var_rat = {1 , 2 };

  int *num_ip = &var_rat.numerator ;
  /*Store the address of numerator , in
    the pointer num_ip*/
  int *den_ip = &var_rat.denominator ;
  /*Store the address of denominator ,
    in the pointer den_ip */

  printf("%d\n" , num_ip < den_ip );
  /*numerator is declared before denominator ,
   in struct rational , as such its address
   has a lower order .
   Output : 1 .*/

  union search var_ser;

  int *close_ip = &var_ser.close_i;
  int *far_ip = &var_ser.far_i;

  printf("%d\n" , close_ip <= far_ip );
  /*close_i and far_i , are member of the
    same union , as such their address
    have the same order ,
    Output : 1 .*/
  printf("%d\n" , close_ip < far_ip );
  /*close_i and far_i , are member of the
    same union , as such their address
    have the same order ,
    Output : 0 .*/

  int arr_i[] = {1 };

  printf("%d\n" , arr_i < arr_i + 1 );
  /*address last element of array
    plus one , has higher order than
    preceding element .
    Output : 1 .*/
  printf("%d\n" , num_ip <= &var_rat.numerator );
  /*Pointers same member of structure ,  are
    equal .*/}

Comparing pointers for equality

When using the equality == or difference operators!= , two pointers are equal , if the address stored has the same numeric value , and the same type .

If one operand is a pointer , and the second one is a null pointer constant ,then the null pointer constant is cast to the pointer type , before performing comparison .

If one operand is a pointer to an object type , and the other operand is a qualified or unqualified pointer to void , then the object pointer is cast to a qualified or unqualified pointer to void .

#include<stdio.h>


int main(void ){
  int var_i = 1;
  int *ptr_i = &var_i;
  int *ptr1_i =	&var_i;

  double var_d = 1.0;
  void *ptr_v = &var_d;

  printf("%d\n" , ptr_i == ptr1_i );
  /*Both ptr_i , and ptr1_i , address 
    contain the same numeric value , 
    and type , they are equal
    Output : 0.*/

  printf("%d\n" , ptr_i == 0 );
  /*The constant pointer literal
    0 , is cast to a null pointer
    literal of the type int . 
    A null pointer is not equal 
    to a non null pointer , as such
    the result is false .
    Output : 0.*/

  printf("%d\n" , ptr_i == ptr_v );
  /*ptr_v is a void pointer , ptr_i
    is cast to a void pointer , the
    numeric value of the address are
    not equal , as such the result
    is false .
    Output : 0 .*/ }

Logical operators

The logical operators , and && , or || , not ! , can be used with the scalar types . The scalar types are the integer types , the floating point types , and the pointer types .

&& will yield 0 , if any of its operands is 0 .

|| will yield 1 , if any of its operands is 1 .

! will yield 1 , if its operand is 0 , or 1 otherwise .

So in the case of pointers , if the pointer is a null pointer , then && will yield false or 0 , if the pointer is not a null pointer , then || will yield true , or 1 . As for ! , it will yield 0 , if the pointer is not a null pointer , and it will yield 1 if the pointer is the null pointer .

#include<stdio.h>

int main(void ){
  int var_i = 1 ;
  int *ptr_i = 0;
  if(!ptr_i )
    /* Apply , the not operator ,
      on ptr_i . ptr_i is the null
      pointer , it has a value of 0 ,
      as such after applying the not
      operator , it will have a value of
      1 . When 1 , if execute , the
      following statement , which initialize
      the pointer to the address of
      var_i .*/
    ptr_i = &var_i;
  if(ptr_i || 0 )
    /* ptr_i , is not null , as such
      || does not evaluate the ,
      second expression , and returns
      1. On 1 , if exceutes the
      following statement , which
      prints the value found , 
      in ptr_i .
      Output : 1 .*/
    printf("%d\n", *ptr_i );
  if(ptr_i && 0 )
    /* ptr_i is not a null pointer ,
      && evaluates 0 . On 0 ,
      it returns 0 .
      If , on 0 , does not execute , 
      the next statement , hence
      printf is not executed .*/
    printf("%d\n", *ptr_i );}

Addition

For pointer types , using the addition operator , + , is defined when , one operand is of an integer type , and the second operand is a pointer to a complete object type .

A complete object type , is an object which has a size . For example , the void type is not a complete object type , because it does not have a size , a structure which is declared , but not defined , so which does not have a body , is another example of an incomplete type .

struct t_s;

When adding an integer value , to the numeric value stored in a pointer , it is not the integer value which is added , but the integer value , multiplied by the size of the object , that the pointer points to .

For the addition of an integer to a pointer , to be valid , the pointer must be a pointer to an object member of an array , or to an element past the last object member of an array , and the result of the addition , must be a pointer to an object of the array , or an element one past the last object of the array , or else pointer addition is not defined .

The element past the last object member of the array , is not necessarily a null pointer , but in all cases it must not be dereferenced using the * operator .

#include<stdio.h>

int main(void ){

  float arr_f[] = {1.0f , 2.0f };
  float *ptr_f = &arr_f[0 ];

  printf("sizeof(float ) : %zd\n\n" , sizeof(float ));
  /*Print the size of the type of the object
    pointed by ptr_f */

  printf(" %p : ptr_f\n %p : ptr_f+0\n %p : ptr_f+1\n %p : ptr_f+2 \n" ,
                ptr_f , ptr_f + 0 , ptr_f + 1 , ptr_f + 2  );
  /*Perform pointer addition , and print the
   successive addresses .
   add 0 , 1 , and 2 , to ptr_f . 
   ptr_f points to the first element ,
   of the array arr_f . The addition , 
   is defined , as long as
   the gotten pointer , is a pointer to an
   element of the array arr_f , or one past ,
   the last element , of the array arr_f .*/


  printf("ptr_f + 2 == (void * ) 0 -? %d \n\n\n" , (ptr_f + 2 )  == 0 );
  /*The element past the last object ,
    member of an array , is not
    necessarily the null pointer ,
    and it must not be dereferenced  .*/

  char *ptr_c = (char * ) ptr_f;
  /*Cast the pointer ptr_f , to a
    pointer to a char .*/

  printf("sizeof(char ) : %zd\n\n" , sizeof(char ));
  /*Print the size of the type
    of the object , pointed by
    ptr_c .*/

  printf(" %p : ptr_c\n %p : ptr_c+0\n %p : ptr_c+1\n %p : ptr_c+2 \n"
         " %p : ptr_c+3\n %p : ptr_c+4\n %p : ptr_c+5 \n"
         " %p : ptr_f+6\n %p : ptr_c+7\n %p : ptr_c+8  \n\n\n"
         , ptr_c , ptr_c + 0 , ptr_c + 1 , ptr_c + 2 , ptr_c + 3 , ptr_c + 4
         , ptr_c + 5 , ptr_c + 6 , ptr_c + 7 , ptr_c + 8 );
  /*Perform pointer addition . The pointer is now a pointer
    to an object of type char . The addition is still valid ,
    as long as the gotten pointer , is a pointer to
    an object in the array , or one past the last
    oject in the array . The array is now interpreted ,
    as being , an array of characters .*/}

/*Output : 
sizeof(float ) : 4

 0x7fff5230c8c0 : ptr_f
 0x7fff5230c8c0 : ptr_f+0
 0x7fff5230c8c4 : ptr_f+1
 0x7fff5230c8c8 : ptr_f+2 
ptr_f + 2 == (void * ) 0 -? 0 


sizeof(char ) : 1

 0x7fff5230c8c0 : ptr_c
 0x7fff5230c8c0 : ptr_c+0
 0x7fff5230c8c1 : ptr_c+1
 0x7fff5230c8c2 : ptr_c+2 
 0x7fff5230c8c3 : ptr_c+3
 0x7fff5230c8c4 : ptr_c+4
 0x7fff5230c8c5 : ptr_c+5 
 0x7fff5230c8c6 : ptr_f+6
 0x7fff5230c8c7 : ptr_c+7
 0x7fff5230c8c8 : ptr_c+8 */

Subtraction

Subtraction is defined for pointers , when the subtraction , is the subtraction of one pointer to a complete object type , from another pointer to a complete object type . Subtraction is also defined for pointers , when subtracting a pointer to a complete object type , from an integer value . A complete object type , is one that has a size .

For subtraction of two pointers to be valid , they must point to objects that belong to the same array . Pointing to the element , past the last object that belongs to an array , is also permissible .

The result of subtracting , a pointer from another one , is of type ptrdiff_t . This result is the distance between the two pointers , as a count of a size , of the object type of these two pointers . ptrdiff_t is defined , in the stddef.h header .

#include<stdio.h>

int main(void ){

  int arr_i [] = {0 , 1 };
  int *ptr_i = &arr_i[0 ];

  printf("%td\n" , ptr_i - &arr_i[1 ]);
  /*Print the difference between
    the two pointers . The result is
    of the type ptrdiff_t , hence the use
    of %td .
    Output : -1 .*/

  printf("%td\n" ,  &arr_i[2] - ptr_i );
  /*Print the difference between
    the two pointers . The distance from
    one past the last object , member of
    the array , to the first object member
    in the array is 2 int , since the pointers
    are of type int .
    Output : 2 .*/


  char *ptr_c =	(char * )ptr_i ;
  printf("%td\n" ,  (char * ) &arr_i[2] - ptr_c );
  /*Cast ptr_i , to a pointer to a char .
    Print the difference , between one
    past the last object member of the
    array , now interpreted as a char ,
    and the first object member of the
    array .
    Output : 8 .*/}

For subtraction of a pointer , from an integer , to be valid , the pointer must be a pointer to an object , of an array , or to an element past the last object member of an array , and the result must be a pointer to an object , of the array , or it must point , to an element one past the last object , of the array .

Subtracting by an integer from a pointer , is subtracting the integer , multiplied by the size of the object , that the pointer points to , from the numeric value , of the address stored in the pointer .

#include<stdio.h>

int main(void ){

  unsigned char	arr_uc[] = {0 , 255 , 0 , 255 };
  /*Declare an array of type unsigned char , 
    and initialize it .*/

  unsigned char  *ptr_uc = (unsigned char * ) &arr_uc[4 ];
  /*Get a pointer , to one past the last
    object  , member of the array 
    arr_uc .*/

  int length_arr_uc = sizeof(arr_uc ) / sizeof (unsigned char );
  /*Calculate the length of the 
    array of type , unsigned char .*/

  for(int i = length_arr_uc ; i >= 0 ; i-- ){
  /*Print the address , and value if any , 
    of address accessible using subtraction
    by an integer from a pointer , which points
    to one past the last object , member
    of the array .*/
    printf("%p : " , ptr_uc - i );
  if(ptr_uc - i != ptr_uc )
    printf("%u" , *(ptr_uc -i ));
  printf("\n" );}}

/*Output : 
0x7fff559338e8 : 0
0x7fff559338e9 : 255
0x7fff559338ea : 0
0x7fff559338eb : 255
0x7fff559338ec :     */

When performing addition and subtraction on pointers to complete object types , a pointer to a complete object , which is not a member , or one past the last element of an array , acts as if the object , is an array of length one , and of a type , the type of the object , that the pointer points to .

#include<stdio.h>

int main(void ){

  int var_i = 2002874948 ;
  /*Declare an int variable ,
    having a value of 2002874948 .*/

  int size_of_var_i = sizeof(var_i );
  /*Get the size of the int 
    variable . The size is 
    returned in bytes . 
    1 char , has a size of 1 byte .*/

  char *ptr_c = (char *) &var_i;
  /*Get a pointer to the int 
    variable , and cast it to
    pointer , to a char .*/

  for(int i = 0 ; i < size_of_var_i ;  i++ )
    printf("%c" , *(ptr_c + i ));}
/*output :
Draw */

Postfix and prefix , increment and decrement operators

The postfix , and prefix , increment and decrement operators : ++ , -- , can be used on pointer types .

When incrementing or decrementing a pointer , the address stored in the pointer , is incremented or decremented by the size of the object , that the pointer points to .

The result of incrementing and decrementing using the postfix operators , is only accessible from the next statement , so on the statement , where they are being executed , the postfix increment , and decrement operators , return the operand value unchanged .

The prefix , increment and decrement operators , increment or decrement the operand , and return the incremented or decremented operand .

#include<stdio.h>

int main(void ){
  char array_c[ ] = "aa";
  /*Create an array of length 3 ,
    initialized with the characters
    a a , and terminated  with
    the null character . The null
    character , has all of its
    bits , set to 0 .*/

  char *ptr_c = &array_c[0 ];
  /*ptr_c is a pointer of
   typ char , it contains ,
   the address of the first
   object , member of the array
   array_c .*/

  while(ptr_c && *ptr_c != '\0' ){
    printf("%#2x\n" , *ptr_c++);}
  /*Print the hexadecimal representation ,
    of the data stored in array_c .
    The pointer is incremented using ,
    the postfix operator , hence 
    ptr_c value is only incremented , 
    starting the next statement . 
    Output :
    0x61
    0x61 */
  printf("\n" );

  ptr_c = &array_c[0 ];
  /*Rewind the pointer , 
    to the address of the 
    first object , stored in 
    array_c .*/

  while(ptr_c && *ptr_c != '\0' ){
    printf("%#2x\n" , *++ptr_c);}
  /*ptr_c contains the address 
    of the first object , member
    of the array array_c . 
    The while statement , loops
    through array_c elements , using
    the prefix increment operator , 
    the address stored in ptr_c , is
    first incremented . Next , it is
    dereferenced . 
    Output : 
    0x61
    0 */ }

Multiplication , division

Multiplication and division only applies to the integer and floating point types , as such it does not apply to the pointer types .

Bitwise operations

Bitwise operations , only apply to the integer types , as such they are not defined for the pointer type .

The conditional operator

The conditional operator , has the format :

Operand_One?Operand_Two:Operand_Three

Operand_One must be a scalar . The scalar types in C , are the integer , the floating , and the pointer types .

If a pointer is to be used as the second or the third operand , then :

Either , one operand is a pointer to an object , and the second operand is a pointer to a qualified or unqualified void pointer . In this case , the pointer to the object is converted , to the qualified or unqualified version , of the void pointer .

int *ptr_i ;
const void *ptr_v ;
ptr_v = 1 ? ptr_i : ptr_v ;
/*When the first operand is  1 ,
  the second operand is evaluated .
  ptr_i is cast using 
  (const void * ) .*/

Either , one operand is a pointer to a C type , and the second operand is a null pointer constant . In this case the null pointer constant is converted , to a pointer of a type , of the pointed to , C type .

volatile short *ptr_vs; 
ptr_vs  = 0 ? ptr_vs : 0 ;
/*When the first operand is 0 , 
  the third operand is evaluated.
  The result is a volatile
  short null pointer . */

Either , both pointer operand , have the same C type , but with different or same type qualifier . The result is a pointer , pointing to the same type , having all the qualifiers .

volatile int *ptr_vi;
const int * ptr_ci;
const volatile int *ptr_cvi = 1 ? ptr_vi : ptr_ci ;
/*When the first operand is 1 , 
  the second operand ptr_vi is 
  evaluated.
  The result is a constant volatile 
  int pointer . */

Arrays

An array is an aggregate type , casting to an array type , of the like (int [ ]) , or (int [3 ]) , cannot be performed . Casting can be only performed to a scalar type , such as (int * ) . The scalar types are , the integer types , the floating point types , and the pointer types .

Bitwise operators such as shifting << , do not apply to an array , they only apply to an integer type .

Multiplication and division , only apply to the integer and floating point types , as such they don't apply to arrays .

An array acts as a pointer , when it is being passed to a function , when performing comparison for equality such as != , or for order such as <= . An array is also treated as a pointer , when perform logical operation , such as && and when performing addition , + and subtraction - .

The pointer that the array acts as , is the address of the first element of the array , This is a constant pointer , so its address cannot be changed .

#include<stdio.h>

int add(int *ptr_i , int length ){
  int sum = 0 ;
  for(int i = 0 ; i < length ; i++ )
    sum += *(ptr_i + i );
  return sum;}

int main(void ){
  int arr_i[] = {1 , 2 , 4 , 6};
  /*Create an int array , containing
    4 elements .*/

  printf("sizeof(arr_i ) : %zd bytes\n", sizeof(arr_i ));
  /*Print the size of the array ,
    Output :
    sizeof(arr_i ) : 16 bytes */

  const int *cptr_i = arr_i;
  /*When assigning an array to a pointer ,
    the array act as a constant pointer to
    its first element .*/

  printf("sizeof(cptr_i ) : %zd bytes\n", sizeof(cptr_i ));
  /*Prints the size of the pointer , not the size
    of the array .
    Output :
    sizeof(cptr_i ) : 8 bytes */

  printf("Number of elements in array : %zd\n" , sizeof(arr_i ) / sizeof(arr_i[1 ] ));
  /*Print the number of elements in the array .
    This can be gotten by dividing the size of the
    array , which is 16 bytes , by the size of
    an element in an array. 
    The size of an int on this machine is 4 bytes ,
    as such the number of elements is 4 .
    Output :
    Number of elements in array : 4 */

  int * ptr_i = 0;
  if(ptr_i != arr_i )
    ptr_i = arr_i;
  /*When performing comparison
    operations , the array
    acts as a constant pointer , to
    its first element .
    The null pointer is different
    from a not null pointer , hence
    the assignment operation is 
    performed , and ptr_i is a 
    pointer , to the first
    element of the array .*/

  printf("2nd element of array is : %d\n" , *(arr_i + 3 ));
  /*Print the value of the second ,
   element of the array . When performing
   addition or subtraction on an array ,
   it acts as constant pointer to its
   first element . This is
   pointer addition or subtraction .
   Output :
   2nd element of array is : 6 */

  printf("sum elements array : %d\n" , add(arr_i , 4 ));
  /*When passed to a function , the array acts
    as a constant pointer , to its first element .
    The recieving function parameter , get the
    refered address .
    The add function calculates , the sum of the
    elements , of the array .
    Output :
    sum elements array : 13 */

 if( (arr_i < arr_i + 1 ) && arr_i )
    printf("True\n" );
  /*When using order comparison < <= >= > ,
    the array acts as a constant pointer ,
    to its first element , the address
    of the first element is less than
    the address of the second element
    of the array .
    && evaluates its first operand ,
    which returns 1 . Since the first
    operand returned 1 , && 
    evaluates , its second operand . 
    The second operand is arr_i  , since
    this is a logical operation , arr_i
    acts as a constant pointer to its
    first element , the gotten pointer 
    is not a null pointer , hence the 
    second operand of && evaluates to 1 ,
    as such && evaluates to 1 .
    When 1 , the if statement ,
    executes , the next statement ,
    which prints True .
    Output:
    True .*/}

An array cannot be incremented , or decremented , using the postfix and prefix increment ++ and decrement -- , operators .

Structures , Unions

Casting only applies to the scalar types , the scalar types are the integer types , the floating point types , and the pointer types , as such it does not apply to structures and union .

It is not possible to use the order , or the equality , or the bitwise , or the logical , or the multiplication and division , or the addition and subtraction , or the postfix and prefix increment and decrement operators , with structure and unions .

Structures and unions can be the second , and third operand of the ternary operator ?: , in such case , they must have the same type .