• No results found

Files and Preprocessor directives / Pointers

Learning Objectives

After completing this session, you will be able to:

‰ Access files in both sequential and random order

‰ Define pre-processor directives

‰ Perform pre-processor operations

‰ Perform conditional compilation

‰ How to declare and initialise Pointers

‰ Understand Pointer Arithmetic

‰ Perform operations on Pointers and Arrays

Random File Operations

The functions discussed earlier are to be used for reading and writing data sequentially. In some applications, it may be necessary to access some part of the file directly. This can be achieved by using the functions fseek(), ftell() and rewind().

ftell()

This function takes a file pointer and returns a long int, which corresponds to the current file pointer position. If it is a binary stream, then the value is the number of bytes from the beginning of the file.

If it is a text stream, then the value is a value usable by the fseek() function to return the file position to the current position. On success, the current file position is returned. On error, the value -1L is returned and error number (errno) is set.

General Form:

n = ftell(fptr);

fseek()

This function sets the file position to the given offset (specified in long integer format).

General Form:

fseek( fptr, offset, from_where)

The argument offset signifies the number of bytes to seek from the given ‘from_where’ position.

The argument from_where can be:

SEEK_SET Seeks from the beginning of the file.

0

SEEK_CUR Seeks from the current position. 1

SEEK_END Seeks from the end of the file. 2

On a text stream, from_where should be SEEK_SET and offset should be either zero or a value returned from ftell(). The end-of-file indicator is reset. The error indicator is NOT reset. On success, zero is returned. On error, a nonzero value is returned.

Example 15.1

fseek (fp, 0L, 0); Move the file pointer to the beginning.

fseek (fp, 0L, 2); Move the file pointer to the end of file.

fseek (fp, 10L, 0); Move after 10 bytes from the beginning.

fseek (fp, 10L, 1); Move after 10 bytes from the current fseek (fp, -10L, 1); Move backward 10 bytes from the current fseek (fp, -10L, 2); Move backward 10 bytes from the EOF.

rewind()

This function sets the file position to the beginning of the file of the given stream. The error and end-of-file indicators are reset.

General Form:

rewind(fptr);

Preprocessor Directives

One of C's most useful features is its preprocessor. Preprocessor directives are lines included in the code that are not program statements but directives for the preprocessor. These lines are always preceded by a pound sign (#). The preprocessor is executed before the actual compilation of code begins, therefore the preprocessor digests all these directives before any executable code is generated for the statements.

Preprocessing is a step that takes place before compilation that lets you to:

‰ Replace preprocessor tokens in the current file with specified replacement tokens.

‰ Embed files within the current file

‰ Conditionally compile sections of the current file

‰ Generate diagnostic messages

‰ Remove the blank lines in the program, change the line number of the next line of source and change the file name of the current file.

‰ Remove comments from the source file.

A token is a series of characters delimited by white space. The white space allowed on a

preprocessor directive may be the space, horizontal tab, vertical tab, form feed, or carriage return.

The preprocessed source program file must be a valid C program.

Preprocessor directives begin with the # token followed by a preprocessor keyword. The # token must appear as a first character. The # is not part of the directive name and can be separated from the name with white spaces.

A preprocessor directive ends at the new-line character unless the last character of the line is the \ (backslash) character. If the \ character appears as the last character in the preprocessor line, the preprocessor interprets the \ and the new-line character as a continuation marker. The

preprocessor deletes the \ (and the following new-line character) and splices the physical source lines into continuous logical lines. No semicolon (;) is expected at the end of a preprocessor directive.

Except for some #pragma directives, preprocessor directives can appear anywhere in a program.

Preprocessor Directives

Name Action

# Null directive specifying that no action be performed.

#define Defines a preprocessor macro.

#elif Conditionally includes source text if the previous #if, #ifdef, #ifndef, or #elif test fails.

#else Conditionally includes source text if the previous #if, #ifdef, #ifndef, or #elif test fails.

#endif Ends conditional text.

#error Defines text for a compile-time error message.

#if Conditionally includes or suppresses portions of source code, depending on the result of a constant expression.

#ifdef Conditionally includes source text if a macro name is defined.

#ifndef Conditionally includes source text if a macro name is not defined.

#include Inserts text from another source file.

#line Supplies a line number for compiler messages.

#pragma Specifies implementation-defined instructions to the compiler.

#undef Removes a preprocessor macro definition.

Preprocessing Operations:

Pre processing operations are mainly classifieds into 1) File Inclusion, 2) Macro substitution and 3) Conditional Compilation.

Preprocessing will be done before compilation, compilation process operates on the preprocessor output, which is then syntactically and semantically analyzed and translated, and then linked as necessary with other programs and libraries.

File Inclusion

The #include directive allows external files to be added in to our source file, and then processed by the compiler.

General Form:

#include <header file> OR #include “header file”

The only difference between both expressions is the places (directories) where the compiler is going to look for the included file.

If the file name is enclosed between angle-brackets <>, the file is searched in the directories where the compiler is configured to look for the standard header files. Therefore, standard header files are usually included in angle-brackets, while other user specificed header files are included using quotes.

In the second case where the file name is specified between double-quotes, the file is searched first in the current working directory. In case that it is not there, the compiler searches the file in the default directories where it is configured to look for the standard header files.

Example 15.2

#include <stdio.h>

#include “stdio.h”

Preprocessor Macros:

#define preprocessor directive is used to define a macro that assigns a value to an identifier. The preprocessor replaces subsequent occurrences of that identifier with its assigned value until the identifier is undefined with the #undef preprocessor directive, or until the end of the program source is reached, whichever comes first.

There are two basic types of macro definitions that you can use to assign a value to an identifer:

Object-like Macros (Symbolic constants)

Replaces a single identifier with a specified token or constant value.

Function-like Macros Associates a user-defined function and argument list to an identifier.

When the preprocessor encounters that identifier in the program source, the defined function is inserted in place of the identifier along with any corresponding arguments.

Symbolic Constants

The preprocessing directives #define and #undef allow the definition of identifiers which hold a certain value. These identifiers can simply be constants or a macro function.

#define

General Form:

#define symbolicvaraiablename value

Example 15.3

#define SIZE 10

#define NAME “xyz” /* good practice is to use upper case letters */

#undef:

General Form:

#undef variablename

Example 15.4

#undef SIZE

Macros:

General Form:

#define macroname(argument list) macrodefn

Example:

#define sqarea(a) ((a)*(a)) main()

{

areaofsquare=sqarea(a);

…..

}

Arguments in the macro definition are enclosed with parenthesis to avoid miscalculation.

Continuation character for macro definition is \. There is no need for semicolon after the macro definition.

Example 15.5

#define sqarea(a) ((a)*(a))

#define sqa(b) b*b

#define add(a,b) ((a)+(b));

main() {

areaofsquare=sqarea(a); /* areaofsquare = (a) * (a); */

areaofsquare=sqarea(3); /* areaofsquare = (3) *(3); */

areaofsquare=sqarea(3+4); /* areaofsquare=(3+4)*(3+4); */

areaofsquare=sqa(3+4); /* areaofsquare=3+4*3+4; Æ(1) */

addition=add(2,3); /* addition=(2)+(3);; Æ(2) */

}

(1) Æ miscalculation because of no parentheses (2) Æ two semicolons in macro expansion.

Conditional Compilation Directives:

A preprocessor conditional compilation directive causes the preprocessor to conditionally suppress the compilation of portions of source code. These directives test a constant expression or an identifier to determine which tokens the preprocessor should pass on to the compiler and which tokens should be bypassed during preprocessing. The directives are:

‰ #if

The directives #ifdef and #ifndef allow conditional compiling of certain lines of code based on whether or not an identifier has been defined. For each #if, #ifdef, and #ifndef directive, there are zero or more #elif directives, zero or one #else directive, and one matching #endif directive. All the matching directives are considered to be at the same nesting level.

General Form:

The compiler only compiles the code after the #if expression if the constant_expression evaluates to a non-zero value (true). If the value is 0 (false), then the compiler skips the lines until the next

#else, #elif, or #endif. If there is a matching #else, and the constant_expression evaluated to 0 (false), then the lines between the #else and the #endif are compiled. If there is a matching #elif, and the preceding #if evaluated to false, then the constant_expression after that is evaluated and the code between the #elif and the #endif is compiled only if this expression evaluates to a non-zero value (true).

Example 15.6

Check whether a variable is defined. If so, change the value of that variable to 1 after undefining it.

#if define(NUMBER)

#undef NUMBER

#define NUMBER 1

#endif

# and ## operators

# Æ causes the argument to be converted as a string enclosed within quotes.

Example 15.7

#define name(x) #x main()

{

…..

printf(name(xyz)); /* printf(“xyz”); */

….

}

## Æ concatenation operator

Example 15.8

#define name(x,y) x##y main()

{

…..

printf(name(ssn,somca)); /* printf(“ssnsomca”); */

….

}

Introduction to Pointers

Pointer is a variable that contain the memory address of another variable. Pointers are one of the powerful and frequently used features of C, as they have a number of useful applications.

Variables contain the values and pointer variables contain the address of variables that has the value. Variable directly references the value and Pointer variable indirectly references the value.

Referencing a value through a pointer is called Indirection.

Whenever a variable is declared, memory is allocated for the variable according to the data type specified.

int a = 5 ; Æ 2 bytes of memory is allocated for variable ‘a’

printf(“ Value = %d”, a); Æ prints the value 5 printf(“ Address of a = %u”, &a); Æ prints the address 1000

Declaration and Initialization

A pointer variable is declared with an asterisk before the variable name. The type-specifiers determine that what kind of variable the pointer variable points to.

Declaration

General Form:

data-type *pointer-name;

C provides two operators, & and *, for pointer implementation.

‰ & Î address operator. It is a unary operator that returns the address of its operand.

‰ * Î Indirection or de-referencing operator. It returns the value of the variable to which its operand points.

‰ * and & are inverse of each other.

Example 15.9 int x, *px;

px = &x; x = 5 ;

x px Æ variables

5 1000 Æ values

5

1000

a a – variable, 5 – value, 1000 – assumed as the address of a

1000 3000 Æ addresses

Example 15.10

Now execute the following printf statements and observe the results.

printf(“ x = %d “ , x); Æprints 5 printf(” address of x = %d “ , &x); Æprints 1000 printf (“ address pointed by pointer = %u”, px); Æprints 1000

printf (“address of the pointer = %u”, &px); Æprints 3000 printf (“content pointed by pointer = %d”, *px); Æprints 5

Initialization

Pointer variables should be initialized to 0, Null or an address. No other constant can be initialized to a pointer variable. Pointer variable of a particular data type can, hold only the address of the variable of same data type.

Example 15.11

Valid and Invalid pointer assignments int a , b , *p = &a , *q = NULL; Æ valid.

q = p; Æ valid - both p and q is pointing to the memory location of variable a b = &a; Æ invalid – ordinary variables cannot hold address.

q = a; Æ invalid - cannot assign value to the pointer variable

Pointer Arithmetic

Pointer Addition or subtraction is done in accordance with the associated data type.

‰ int Æ adds 2 for every increment

‰ char Æ adds 1 for every increment

‰ float Æ adds 4 for every increment

‰ long int Æ adds 4 for every increment

All the operations can be done on the value pointed by the pointer.

The following operations can be performed on pointer variables:

‰ A pointer variable can be assigned the address of an ordinary variable or it can be a null pointer.

‰ A pointer variable can be assigned the value of another pointer variable.

‰ An integer quantity can be added to or subtracted from a pointer variable.

‰ One pointer can be subtracted from another pointer variable provided both are pointing to same array.

‰ Two pointer variables can be compared.

The following are the illegal operations on pointers variables:

‰ Two pointer variables can not be added.

‰ Pointer variable can not be multiplied or divided by a constant.

Example 15.12: Pointer arithmetic int * ptr , i=5;

ptr= &i; Æ let ptr = 1000 (location of i) ptr ++; Æ ptr = 1002 (+2 for integers)

++*ptr or (*ptr)++ Æ increments the value of i by 1

Example 15.13: Pointer operations Legal operations

p1 > p2 p1==p2 p1+2 p1-p2 (if p1, p2 points to same array) Illegal operations

p1/p2 p1*p2 p1+p2 p1/5

Pointers and Arrays

Arrays

Array is used to store the similar data items in contiguous memory locations under single name.

Array addressing is in the form of relative addressing. Compiler treats the subscript as a relative offset from the beginning of the array. Array subscripting notation is converted to pointer notation during compilation, so writing array subscripting expressions using pointer notation can save compile time.

Pointers

Pointer addressing is in the form of absolute addressing. Exact location of the elements can be accessed directly by assigning the starting location of the array to the pointer variable. The pointer variable is incremented to find the next element.

‰ C treats the name of the array as if it is a pointer to the first element. Thus, if v is an array, *pv is the same as v[0], *(pv+1) is the same as v[1], and so on.

Pointer pointing to an array

Initialization

To initialize a pointer variable, conventional array is declared and pointer variable can be made to point to the starting location of the array. Array elements are accessed using pointer variable.

General Form:

pointer_variable = &array_name [starting index];

OR

pointer_variable = array_name;

Example 15.14

int a[5] = {1,2,3, 4,5} , *ptr , i ; ptr = a ; Æ similar to ptr = &a[0];

Assume that array starts at location 1000

&a[0] = 1000 a[0] = 1 ptr + 0 = 1000 *(ptr+0) = 1

Pointers and Multi Dimensional Arrays

As the internal representation of a multi dimensional array is also linear, a pointer variable can point to an array of any dimension. The way in which the pointer variable used, varies according to the dimension.

General Form:

ptr_vble = &array_name [starting index1]…[starting indexn];

OR

ptr_vble = array_name;

Example 15.17

int a[2][2] = {1,2,3,4} , *ptr ; ptr = &a[0][0] ;

Assume that the array starts at location 1000

&a[0][0] = 1000 a[0][0] = 1 ptr+0 = 1000 *(ptr+0) = 1

&a[0][1] = 1002 a[0][1] = 2 ptr+1 = 1002 *(ptr+1) = 2

&a[1][0] = 1004 a[1][0] = 3 ptr+2 = 1004 *(ptr+2) = 3

&a[1][1] = 1006 a[1][1] = 4 ptr+3 = 1006 *(ptr+3) = 4

If the pointer to the array is accessed with 2 subscripts, it results in a problem.

For example,

(p+0) + 1 Æ if it is used to represent 0th row and 1st column and

(p+1) + 0 Æ if it is used to represent 1st row and 0th column results in p+1.

So, multi dimensional arrays can be represented by pointer in the following two ways:

‰ Pointer to a group of arrays

‰ Array of pointers

Pointer to a group of arrays

A two dimensional array, for example, is a collection of one dimensional array. Therefore, a two-dimensional array is defined as a pointer to a group of one two-dimensional array and in the same way three dimensional arrays can be represented by a pointer to a group of two dimensional arrays.

int a[3][2] can be represented by a pointer as follows:

int (*p)[2] p is a pointer points to a set of one dimensional array, each with 2 elements.

Note: First dimension need not be specified but the second dimension has to be specified. Here, a single pointer is used and it needs to know how many columns are there in a row.

The following representations are used when a pointer is pointing to a 2D array:

‰ ptr+i is a pointer to ith row.

‰ *(ptr+i) refers to the entire row - actually a pointer to the first element in i th row.

‰ (*(ptr + i) +j) is a pointer to jth element in ith row

‰ *(*(ptr+i) + j)) refers to the content available in ith row, jth column

Accessing value Example 15.18

printf (“%d “,*(*(ptr + i) +j); Æ displays the x(i,j) value printf (“%d “,*(a[ i ] + j); Æ displays the x(i,j) value printf (“%d “,*(a + i)[ j ]; Æ displays the x(i,j) value

Example 15.19 main()

{

int i, j;

int a[2][3]={1,2,3,4,5,6};

int *pa=&a[0][0];

for (i=0;i<2;i++)

{

for (j=0;j<3;j++)

printf(“\t%d”,*(*(pa+i)+j));

printf(“\n”);

} } Output:

1 2 3 4 5 6

Array of Pointers

Multi dimensional array can also be expressed in terms of an array of pointers.

int a[2][2] can be represented as int *ptr[2]

Here, we have 2 pointers ptr[0], ptr[1] and each pointer can point to a particular row . Thus, only one indirection is enough to represent a particular element.

Example 15.20

int a[2][2] = {1,2,3,4} , *ptr[2] ;

ptr[0] = a[0]; /* ptr[0] is now pointing to the 0th row ( & a[0][0]) */

ptr[1] = a[1]; /* ptr[1] is now pointing to the 1st row ( & a[1][0]) */

ptr[0] + 0 = 1000 *(ptr[0] + 0) = 1 ptr[0] + 1 = 1002 *(ptr[0] + 1) = 2 ptr[1] + 0 = 1004 *(ptr[1] + 0) = 3 ptr[1] + 1 = 1006 *(ptr[1] + 1) = 4

Example 15.21

(1) *p[3] declares p as an array of 3 pointers

(2) (*p)[3] declares p as a pointer to a set of one dimensional array of 3 elements

Pointers and Strings

Character pointer is a pointer, which can hold the address of a character variable. Suppose, if we have a character array declared as:

char name[30] = {“Data Structures”};

We can declare a character pointer as follows:

char *p = NULL;

Once the pointer is declared, the address of the array is assigned to this pointer. When an array is referenced by its name, it refers to the address of the 0th element.

p = name;

The statement assigns the address of the 0th element to p.

Now issue the following printf statements and check the output:

printf(“Character output = %c\n”, *p);

printf(“String output = %s”, *p);

When a pointer variable is referred with the indirection operator, it refers the content of the address pointed by the pointer variable. The above printf statements produce the outputs as follows:

Character output = D

String output = Data Structures

The reason for the output produced by the second printf statement is because of the %s format specifier, which will print the string till it encounters a ‘\0’ character. Pointer automatically gets incremented to the next location.

Character-type pointer variable can be assigned an entire string as a part of its variable declaration.

char *p = “string” ; Æ valid int *p = {0,1,2,3} ; Æ invalid

Thus, string can be represented by either as a one-dimensional character array or a character pointer.

An array of character pointers offers a convenient method for storing strings. Each pointer is used to represent a particular string.

Conventional array declaration: char name[10][10];

Array of character pointers : char *name[10];

Ragged Arrays

Consider the following array declaration.

char names[3][10] = { “abcde”, “rstu”, “xyz”};

This array occupies 30 bytes and the row length is fixed. Instead of making each row a fixed number of characters, make it a pointer to a string of varying length.

If the elements of array are string pointers, a set of initial values can be specified as part of the array declaration.

char *name[4] = { “A” , “AB” , “ABC” , “ABCD”} ;

An advantage is that a fixed block of memory need not be reserved in advance. The above statement allocates variable length block of memory and occupies only 14 bytes. It declares 4

pointers each pointing to a string. Thus, substantial saving in memory. Arrays of this type are referred as Ragged arrays (used only in the initialization of string arrays).

In the above example,

*(name + 1) will access the string AB

* (name + 2) will access the string ABC

*(*(name + i) +j) refers the jth character in ith string

*(*(name+3)+3) refers D in the string “ABCD”

Memory organization – String Pointers Example 15.22

(1) char *ps = “xyz”;

pointer ‘ps’ is stored in 2 bytes and ‘ps’ contains the address of the string that requires 4 bytes.

(2) char s[ ] = “xyz”;

string ‘s’ is stored in 4 bytes.

Pointer to a constant

The address of a constant variable can be assigned to a pointer variable. The following example explains the pointer variable to a constant variable:

Example 15.23 const int a=10;

int *pa = &a;

/* suspicious pointer conversion. Wise to avoid such assignments */

Variable ‘a’ is a constant variable. The value cannot be modified.

Pointer variable ‘pa’ can take any other address and value of ‘a’ can be changed using pointer even though it is constant variable.

Constant Pointer

The pointer variable can be a constant. A pointer variable can take the address of a non-constant data and constant data.

Constant pointer to non-constant data always points to the same memory locations and the data at that location can be modified through the pointer. Pointers variables that are declared ‘const’ must

Constant pointer to non-constant data always points to the same memory locations and the data at that location can be modified through the pointer. Pointers variables that are declared ‘const’ must