2. The Foundation of C – C Programming Essentials

Chapter 2

The Foundation of C

Here, we present the lexical syntax of C. An overview of C keywords is given. In this chapter, you shall have a look at the data types available in C. Presenting variables and constants, we delineate naming conventions for variables and modes of representing constant values along with functions, which are the basic building blocks of a C program. After getting our first look at two standard library functions for input and output, we move on to writing our first C programs using the techniques we discussed. At the end of this chapter, you will also be learning about the scope of variables in conjunction with the different types of storage classes and their usage. For starters, let us look at the memory of a typical desktop computer.

2.1 | MEMORY AND STORAGE

The primary memory or the Random Access Memory (RAM) is the storage area for the program along with all the

working data of a program in execution. Memory consists of cells or locations where data is stored. The typical storage unit of memory is a byte, and a byte consists of eight bits. The structure discussed (Figure 2.1) might vary with the architecture. The storage units of memory are given fixed addresses by which they are referenced. It is possible to write into a storage location in memory or read from it.

 

 

Fig. 2.1 Primary Memory

2.2 | C CHARACTER SET

As in any language (natural or programming), there is a set of characters (symbols) that the C programming language recognizes. A character can be a numeric digit, an alphabet, or any special symbol that is used to convey information. C is a case-sensitive language. Thus, ‘a’ and ‘A’ are not the same in C. The valid characters allowed in C are the following:

  • Alphabets: [A-Z] or [a-z]
  • Numeric digits: [0-9]
  • Special Characters: ’ ” + - * / % ! < > > # < > & | [ ] { } : ;, . ? \ ~ @ _
2.3 | C KEYWORDS

A keyword is a reserved word to the compiler and has a special meaning for the compiler. Every programming language has a large number of keywords and it is difficult to remember them all. The C programming language has only 32 keywords. This aids in easier and simpler programming. Since the meanings of keywords are special to the C compiler, C keyword names cannot be used as variable or function names. A grouped list of all the keywords in C is given in Figure 2.2.

 

Fig. 2.2 C Keywords

 

Notes: * indicates that it is the name of a data type the user has created

     ** indicates absence of a type specifier

 

Learning C is not merely learning the keywords and their uses. Complex applications can be developed from simple forms, and learning keywords is a step to mastering the simple forms of C. As we proceed, we shall discuss the above set of keywords in detail along with the logical grouping (as given by the keyword type field in Figure 2.2) strictly followed in most cases. The order is not rigorous since interdependencies exist and is violated if deemed necessary.

2.4 | WHITESPACES IN C

Any combination of spaces, tabs or newlines is called a whitespace. C is a free-form language as the C compiler chooses to ignore whitespaces. Whitespaces are allowed in any format to improve readability of the code. However, there are a few restrictions in place on the use of whitespaces. For example, it is not permissible to insert whitespaces as part of constants or variables, as we shall see in the following sections related to constants and variables. Before that, let us get acquainted with the data types C has to offer.

2.5 | DATA TYPES

By definition, data are raw facts. For example, 223 is data, and so is 54.22. For data to be meaningful, we must be able to identify the range of permissible values that is represented by the data. Accordingly, every data has its own domain or data type. C has the usual simple data types: characters, integers and numbers with fractional components. In addition, C allows variants on some of these types by the use of type modifiers. These types differ in the sort of information they contain and the amount of storage space allocated to them on different systems.

In C, the domains consist of one of the basic data types in C, one of the basic data types with type qualifiers, or any user-defined data type. We shall not be dealing with user-defined data types in this chapter.

2.5.1 | Basic Data Types in C

The basic data types in C are char, int, float, and double. Another basic data type is void, which we shall encounter while learning about functions. As a data type, void is not discussed in this section.

char   This is to store a single character from the character set. The length of a char variable allocated by C is one byte. This allows at most 256 character values. A character is stored in memory in one byte according to a specific encoding. Most machines use either ASCII or EBCDIC character codes. For illustration, we shall be using the ASCII code for all future discussions. A character is considered to have the value corresponding to its ASCII encoding. There is no particular relationship between the value of the character constant representing a digit and the digit's intrinsic integer value, i.e., the ASCII value of ‘8’ is not 8, it is 56.

int   This data type is used for storing an integer, the size of which is typically reflected by the size of integers in the host machine. The size of an int is at least 16 bits. Let us calculate the range of integers given the number of bits allocated for an integer on a given machine.

Let Nmin. represent the smallest integer that can be stored in an int, and let Nmax represent the largest integer that can be stored in an int. If ‘i’ is a variable of type int, then the range of values that ‘i’ can take on is given by

       Nmin ≤ i ≤ Nmax

with the endpoints of the range being machine-dependent. In case of integer representation by 16 bits, the typical situation is:

       Nmin  = -215 = -32768-32 thousand
       Nmax  = 215-1 = 32767+32 thousand

Now, suppose we try to represent a number larger than the maximum possible integer size permitted. For example, if we try to add 1 to 32767 in a machine with 16-bit integers, we will not get 32768. This condition is called an integer overflow. Overflow indicates that we have violated the possible range of values.

float   This data type is used for representing single-precision floating-point numbers. Internally, float values are stored in three components: a sign bit, some number of bits to store the fractional component of the number (mantissa), and the remaining bits to store an exponent part. These components correspond to the three parts of a real number in floating-point representation.

As with integers, the range of values that a floating-point variable can be assigned with, is machine-dependent. If 32 bits are used to store floating-point numbers, the range of values permitted if 24 bits are used for the mantissa, 7 bits for the exponent, and 1 bit for the sign are:

              -3.4*10-38 to -3.4*10+38
      and     +3.4*10-38 to +3.4*10+38

double   The double data type is for representing a double-precision floating-point number. The double values are stored in a fashion similar to floating-point values - a sign bit, a mantissa, and an exponent. Due to use of double precision, the range and number of possible values are greater than in float type since more bits are allocated for representing this type.

A 64-bit double with 52 bits for mantissa, 11 bits for exponent, and 1 sign bit allows representation of values ranging from

              -1.7*10-308 to -1.7*10+308 
       and    +1.7*10-308 to +1.7*10+308

2.5.2 | Basic Data Types with Type Modifiers

The type modifiers that can be used with the basic data types are

  • short: Used for representing an integer whose length is shorter than or equal to that of an int. The length of a short variable is at least 16 bits.
  • long: Used for representing an integer whose length is longer than or equal to that of an int. The length of a long variable is at least 32 bits.
  • signed: Signed numbers are positive, negative, or zero.
  • unsigned: Unsigned numbers are always positive or zero.

2.5.3 | Restrictions on Use of Type Modifiers

Any of the type modifiers cannot be used with any of the four basic data types. The restrictions in place are as follows:

  • short and long modifiers are used with int data types. Examples: short int, long int
  • long int is the same as long, and short int the same as int. (Note that the representation in this case is compiler-dependent.)
  • signed and unsigned modifiers can be applied to either char or int (with or without short and long modifiers), signed modifier is assumed if qualifiers are ommitted.
  • Examples: signed int, signed char, unsigned int, unsigned char
  • long double specifies extended-precision floating point. The size of the variable is implementation-defined, but is guaranteed to be of greater or at least equal precision to that of a double.
2.6 | C CONSTANTS

By definition, a constant is a quantity whose value does not change. As a programmer, we might find the need for a specific value at some point in a program. This may be the only place where we need the value or we might need the value in lots of places, but the value should never change. Depending on its value, a constant will be interpreted as being of a specific type. For example, the letter ‘A’ is a character constant, 52 is an integer constant, and 87.24 is a floating-point constant. We shall now discuss the methods C provides for writing these different constants. As with all program data, constants in C are stored in memory locations. C constants can be divided into two categories:

  • Primary Constants:
    • Integer Constant: It is represented by one or more digits without the decimal point, and with a preceding +ve or -ve sign or no sign (where the constant is assumed to be positive).

      Examples: 123, -56, 0, +1984 are all integer constants.

    • Real Constant:
      • Fractional Form: It is represented by one or more digits with the decimal point, and with a preceding +ve or -ve sign or no sign (where the constant is assumed to be positive).

        Examples: 123.76, -56.05, 0.1, +19.84 are all real constants.

      • Exponential form: Here, we represent the real constant in two parts - the exponent and the mantissa, separated by an ‘e’. The mantissa is similar to the fractional form of representation as discussed above. The exponent part is constructed similar to an integer constant.

        Examples: 123.76e-8, -0.5605e2, 0.1e+1 are all real constants.

    • Character Constant: It is a single alphanumeric or a special symbol enclosed within single quotes. These are also termed as character literals.

      Examples: ‘F’, ‘a’, ‘9’, and ‘%’ are all character constants.

  • Secondary Constants: The representations for secondary constants lie beyond the scope of this chapter and will be presented later. The types of secondary constants are given only for reference. We, however, present representations of string constants.

2.6.1 | More on Primary Constants

Integer constants   Unless otherwise stated, an integer constant is an int. An integer constant too large to fit into an int is taken as long. An integer constant can also be explicitly stated as being long by appending an ‘l’ or an ‘L’ to the end of the constant. 7654321L is a long constant. To create an unsigned constant in C, we append a ‘u’ or a ‘U’ to the constant. For example, we could write 123U and it would be an unsigned constant.

C also makes it easy to write integers in number bases other than base 10 (decimal). In particular, it is sometimes convenient to write a number in base 8 (octal) or base 16 (hexadecimal). Such alternate bases can be more convenient because it is easier to convert a binary representation into one of these bases, rather than into base 10.

When we write an integer, the system has to know whether we are writing it in decimal, octal, or hexadecimal. For this purpose, we prefix a hexadecimal number by a leading ‘0x’ and an octal number by a leading ‘0’. All other integer representations are considered to be in decimal. It is, however, important to note that the program does not print a zero before an octal number or a hexadecimal one when it outputs the number.

Thus, 0xF is in hexadecimal representing decimal 10. Additionally, the extensions of ‘l’, ‘L’ ‘u’ and ‘U’ as applicable to decimal integer constants can be added to create long and unsigned versions of hexadecimal and octal numbers. Hence, 0666431022L will be the long octal number 666431022.

Real constants   Any real constant in C is treated as a double. To write a float constant, we explicitly state so by a terminal ‘f’ or ‘F’. To get an extended-precision floating-point constant, we terminate a real constant by an ‘l’ or an ‘L’. Thus, 12e-1 will be a double, 12.5F will be a float, and 12.987e-6L will be a long double constant.

Character constants   In C, character constants are written in single quotes. We can also specify a character constant in another way. C allows specification of a character by its ASCII code. For example, since the character ‘a’ has an ASCII value of 97, ‘a’ and 97 are equivalent ways of writing the values of the character constant. We have already learnt that integers could be written in bases other than decimal. So, we could have written 0141 to represent the character constant in octal.

Character constants are also written as sequences of two or three symbols in single quotes. These, however, represent single characters. For example, the character constant ‘\0’ is the NULL character with ASCII value zero.

We can also write a character by specifying an octal escape code such as ‘\141’ to represent the character ‘a’, or with its hexadecimal escape code ‘\x61’. An escape code begins with a backslash ‘\’. One important point to note is that it is not required to prefix a ‘0’ in the octal or hexadecimal escape code due to the presence of the backslash. A complete list of escape sequences is given below (Figure 2.3).

 

 

Fig. 2.3 Character-escape Sequences and Their Meanings

2.6.2 | String Constants

A sequence of characters enclosed within double quotes is a string. Note that character and string constants are of different types as abided by in C. For example ‘b’ and “b” are not the same. It is also worth mentioning that a double-quote mark is a single character and not two single quotes. The rules for writing escape sequences are the same in strings as for characters, as shown in Figure 2.3.

Strings are constants. Within a C program, a string is considered as a particular type of constant, technically an array of characters. For now, it is enough to remember that a string is a separate class of constants. Strings will be discussed in detail in the chapter on Arrays.

2.7 | VARIABLES

By definition, a variable is a quantity that can change (accept different values). Every variable has its domain (i.e., the range of permissible values that can be stored in a variable. Thus, a variable is an object that may take on values of a specified type. In C, the domain of a variable has to be declared before the variable can be used. Domains consist of any of the data types discussed in Section 2.5.

We have already learnt about the domain of variables. Additionally, as in mathematics, every variable in C has a name. The name of the variable is also called the variable identifier, or identifier for short. The naming conventions for variables are:

  • A variable is any combination of alphanumerics and underscores where the first character is an alphabet or an underscore.
  • No whitespaces are allowed within variable names.
  • Uppercase and lowercase letters are distinct.
  • The length of a variable name is limited only by the compiler used. Generally, the first 32 characters are significant. For the maximum permissible variable length supported by your compiler, refer to your compiler documentation for details.

A few examples of valid variable names (identifier names), invalid variable names, and reasons for invalidity are given in Figure 2.4.

 

 

Fig. 2.4 Valid and Invalid Variable Names

2.8 | A PEEK AT FUNCTIONS

The C language is based on the concept of building blocks. The building blocks are called functions. A C program is a collection of one or more functions. Functions in C are as in real life, a set of related tasks that are grouped together in a functional unit to enhance the modularity and comprehensibility of the program. So, instead of narrating all the tasks performed on a regular basis in a day of our lives as one single unit, it would be better to modularize the day into valid functional units, distinct from one another. Hence, it would be better to divide the day in the life of a student into modules or functions of eating, travel, school hours, sleep, etc. Functions in C are no different. To write a program in C, we first create functions and then put them together. Functions break large computing tasks into smaller ones. A C program generally consists of many small functions rather than a few large ones.

A function is expected to be called from another function - for example, when you are performing the function of watching a home video and you are asked to run over to the market and buy milk and groceries. The general action taken in such a case (assuming that you are not feeling lazy) is to leave the present function, make a mental note of what you were watching, take the list of the groceries, and enter the function of the chore. While in the function of the chore, we assume two possibilities - the market is open or closed. If the market is open, you buy the goods and return with a success message. If, however, the market is closed, you return home, but with a message indicating failure. In both the cases, after returning from the function of running the errand, you return where you left off, i.e., resume with what you were doing (in this case, watching the home video). We shall return to the analogy later.

2.9 | USEFUL IDENTIFIER-NAMING CONVENTIONS

Some useful variable- and function-naming techniques are suggested below. Although it is not essential to follow the techniques suggested, it is advisable to abide by them.

  • Try using as meaningful a name as possible. A variable name should indicate the purpose of the variable or the context in which it is being used. For example, a variable used to store gross salary might be named ‘Gross-Salary’ rather than ‘JKL’. Also, rather than using all lowercase names, try capitalizing the first letters of each distinct word or separating the words by underscores to improve readability. As stated earlier, spaces are not allowed in an identifier name.
  • Avoid full words in variable names if the name becomes too large, even if the compiler supports relatively lengthy variable names. In such situations, abbreviations of the words could be used instead. For example, to store the balance amount remaining after tax deductions, instead of a name, ‘BalanceAmountRemainingAfter-TaxDeductions’, we could try ‘BalAmtAfterTaxDedc’ or simply ‘BalanceAmt.
  • The trick is to balance the length of variable names by choosing such names that are expressive enough to remember (the name conveys the purpose of the variable) as well as short enough to avoid the rigors of typing long names every time the variable is accessed.
  • Although ‘balanceandBalance’ would be two different variables, such use is not advised to avoid confusion.
  • Use of underscores ‘_’ as starting characters of variable names is not advised as library routines often use such names.
2.10 | TYPE-DECLARATION STATEMENTS

Statements and instructions are the program units contained in functions. In addition to the classifications of programming language instructions in Section 1.8, the C language has type definitions and declaration statements. These statements define and declare the variables for their use in the function and their type.

Putting data types and definitions together, we illustrate the syntax of variable definitions in C.

Formally, the variable-definition syntax is:

       DataType Variable_identifier {, Variable_identifier};

where ‘DataType’ is any of the data types in C discussed earlier, and the following part indicates a list of one or more variable identifiers (names) separated by commas whose domain is the data type stated. The list is generated by the fact that the terms present in braces might be repeated. Like any other C statement, a semicolon is used to terminate the definition.

We now illustrate variable definitions by a few examples.

       int x, y, zl;
       char Section;
       float basic_sal, Gross_Sal; 
       unsigned char z2;

where x, y, and zl are defined to be variables of integer data type, i.e., x, y, and zl can store integer values. The other examples have similar interpretations.

A variable is defined when its properties (data type) have been specified. A variable is said to be defined when it has been declared and the storage for the variable allocated. When a variable is defined but not initialized, the variable contains a garbage value. As of now, it is enough to know that the definition syntax we presented also defines storage for the variable. A variable can be given an initial value on definition. The syntax of initialization at its definition is included to give a more general syntax for definition of the variable. We now write the definition syntax as:

       DataType Variable_identifier < =Constant_expression>
                  {, Variable_identifier < =Constant_expression>};

where DataType is the same as in the earlier definition syntax, and the list following the data type is now a list of variables of the data type separated by commas and having initialization with a constant value, using the assignment ‘=’ operator as an optional part. The terms enclosed in ‘< >’ are optional. Thus, some or all of the variables in the list might be initialized at the point of definition. The ‘Constant_expression’ is an expression consisting of constant values or a single constant. The previous section on C constants guides the syntax of the constant values used. The following examples illustrate the above syntax:

     int x = 10, y, zl = 77 + (88*100);
     char Section = ‘P’, t = 97, g;
     float basic_sal, Gross_Sal = -0.102e2F;
     long z2 = 525353355L;

In the above example, x is defined to be of int data type and initialized by the integer value 10. This provides the definition for x. Gross_Sal is defined to be a single-precision floating-point number and is initialized with –10.2 (—0.102e2). The ‘F’ is added to indicate the constant to be a single-precision floating-point number.

As stated earlier, omission of the ‘F’ defaults the constant to be a double-precision real number. The variable ‘y’ is declared to be an int but not initialized. The identifier ‘t’ is a character variable initialized to 97, which corresponds to ASCII ‘a’. This is permissible as explained in the previous section on constants.

A memory address is associated with a variable at the time of its definition. A variable's type informs the compiler about how the information in the variable is to be stored along with the amount of storage space that must be allocated to store the value of the variable.

2.11 | OPERATORS

The C language is rich in built-in operators. An operator is a symbol that instructs the compiler to perform specific manipulations - arithmetic, logical, or otherwise. C has three general classes of operators, viz. arithmetic, relational and logical, and bitwise. In addition, there are some special operators for particular tasks. Operators can be applied to variables and constants. In other words, variables and constants represent valid operands. All operands are not valid for all operators. There are three types of operators in C according to the number of operands associated with the operator. Accordingly, there are unary operators (operators requiring a single operand), binary operators (operators requiring two operands), and ternary operators (operators requiring three operands).

An expression is a combination of operators and operands (data objects, variables, constants, or expressions) conveying a specific evaluation. Precedence of operators guides the order of evaluation of the operators in an expression. Precedence of the operations is usually indicated by use of parentheses ‘(’, and ‘)’. However, as in arithmetic, all the operators in C, arithmetic or otherwise, have an implicit precedence relation between them. This guides the order of evaluation of expressions in the absence of parenthesis.

It is important to note that the use of whitespaces has no effect on the evaluation of the result of an expression. Another important thing to note is that although the use of parenthesis is not compulsory, use of parenthesis improves readability of the expression and prevents errors caused by different evaluation orders.

Another term related to operators is its associativity. Associativity of an operator gives the order of evaluation of two or more occurrences of the operators in the absence of explicit precedence ordering using parenthesis. Associativity is of two types, viz. right to left and left to right. For example, the addition operator ‘+’ associates left to right, i.e., for the expression

       a + b + c + d

left-to-right associativity converts the expression to

       (((a + b) + c) + d)

where the ‘+’ operators are evaluated from left to right. For right-to-left associative operators, sequences are evaluated from right to left.

We now move on to presenting in detail the operators available in C. Apart from presenting the natural precedence among the operators as we move along, we shall be presenting the precedence table for the entire set of C operators at the end of this section, after all the operators have been discussed.

2.11.1 | Arithmetic Operators

C provides the common binary arithmetic operators of addition ‘+’, subtraction ‘—’, multiplication ‘*’, and division ‘/’. In addition, C also provides the unary minus ‘—’ operator. Each of these works with all of the basic data types in C. The implicit precedence relation between the arithmetic operators is as given in Figure 2.5, in order of decreasing precedence.

 

 

Fig. 2.5 Precedence Table for Arithmetic Operators

 

The following examples (Figure 2.6) demonstrate the arithmetic operators in C. The examples have been constructed with an arithmetic expression and the result of evaluation.

 

 

Fig. 2.6 Arithmetic Operators at Work

2.11.2 | Assignment Operator

The assignment operator in C is ‘=’. It is a binary operator used to assign values to variables. The general form of this operator is:

       LValue = RValue;

where Value is a variable or a storage location. RValue is an expression resulting in a value of the same type as LValue. For situations where the types differ, refer to Section 2.14 (Type Conversion in Assignments) for details. An LValue cannot be, for example, a+22, since a+22 is not a storage location. However, the variable ‘a’ indicates a storage location and can be used as an LValue.

If x and y are int variables and z is a char, and we write in sequence the following statements:

       x = 34 + 45 * 10;
       p = y = 15 + x;
       z = ‘R’;

we shall be assigning the value 484 to x, (15 + 484)=199 to y, 199 to p, and the character ‘R’ to z.

The assignment operator associates from right to left (y = 15 + x is evaluated first, and then the assignment p = y occurs). It has the lowest precedence among all operators except for the comma ‘, ’ operator, as we shall see later.

2.11.3 | Increment and Decrement Operators

In addition to the arithmetic operators, the language allows two very useful operators, which are not commonly available in other languages. They are the increment ‘++’ and the decrement ‘− −’ operators. They are both unary operators. The increment operator adds 1 to the operand, while the decrement operator subtracts 1 from the operand. For example, the statement

       Test = Test + 1;

can be equivalently written in the increment form as

         ++Test;     (prefix form)
  or     Test++;     (postfix form)

The major difference between the two forms of the increment operator as shown above is that the prefix form increments first and then uses the incremented value in the expression, while the postfix form first uses the current value in the expression and then increments the value after use. The following example illustrates the difference between the prefix and the postfix forms of the increment operator.

Let us consider two integer variables, x and y. If x = 5, then the following statements will assign

         y = 6 for y = ++x;     (prefix form)
     and y = 5 for y = x++;     (postfix form)

In both the cases, the value of x becomes 6.

The decrement operator, too, has its prefix and postfix versions. Similar rules apply to these versions of the decrement operator. The increment and decrement operators have left-to-right associativity, and the same precedence as that of the unary minus operator in Figure 2.5.

2.11.4 | Modulus Division Operator

The operator ‘%’ is the modulo division operator in C. It can only be applied to integer types such as int and long, with or without type qualifiers. It does not work with real operands such as float or double. It is a binary operator whose application returns the remainder when an integer divides another. Similar to the arithmetic operators, this operator associates from left to right. For example, the statements

       x = 5;
       z = x % 2;

where x and z are integers and specify that z takes the remainder of the division of 5 by 2. Thus, z is set to 1 after the statements are executed. But if the initial value of x is -5, then the value of z is computed as 1, since the computation is done in the following fashion:

       To compute                        r = a%b,
       the relation                      a = q*b+r(o≤r<b)
       must hold where q is given by     q = floor(a/b)

The precedence of the modulus operator is the same as that for multiplication ‘*’ and division ‘/’ in the preceding table of Figure 2.5.

2.11.5 | Shorthand Operators

C provides a number of shorthand operators, which combine the tasks of carrying out arithmetic operations as well as assignment. These are all binary operators with a variable on the left side and an expression on the right. Although these operators do not provide any extra power to the language, they enable writing of short and compact code with minimum of typing.

The shorthand operators and their usage, along with their equivalents, are tabulated in Figure 2.7.

 

 

Fig. 2.7 Shorthand Operators

 

The shorthand operators associate similarly with the assignment operator (right to left), and have the same precedence.

2.11.6 | Relational and Logical Operators

The relational operators ‘= =’ (equal to), ‘!=’ (not equal to), ‘<’ (less than), ‘<=’ (less than or equal to), ‘>’ (greater than), and ‘>=’ (greater than or equal to) are all binary. The equality operator is indicated by ‘= =’ to avoid confusing it with the assignment operator ‘=’. All the relational operators take two expressions as operands and yield either the value zero (0) or the non-zero value one (1). A zero indicates that the comparison evaluates to false, and a non-zero value indicates an evaluation to true.

Relational operators can be used to form relational expressions. Consider the relational expression x < y. If the value of x is less than y, then the expression evaluates to 1 (true). However, if the value of x is not less than the value of y, then the expression has the value 0 (false). Some examples of relational expressions are:

       a < 5
       p - 99 == 57 * g
       x != 324
       a * 25 < b + c
       -15.9 >= 3.5 + 12.1

To aid in comparisons, C provides three logical operators, viz. ‘!’ (logical not), ‘&&’ (logical and), and ‘||’ (logical or). Of these, ‘!’ is a unary operator with the operand being an expression that can appear to the right of the operator. ‘&&’ and ‘||’ are binary operators that take two expressions (one to the left of the operator and the other to the right of the operator) as operands.

A logical expression with the logical and operator invoked with two operand expressions evaluates to true if and only if both the expressions are true; otherwise, it evaluates to false. The logical or operator evaluates to false if and only if both operand expressions evaluate to false; otherwise, it evaluates to true. The logical not evaluates to true if the solitary operand expression is false, and to false if the expression evaluates to true.

All relational and logical operators associate from left to right, and have a higher priority than the assignment and shorthand operators, and a lower priority than the arithmetic, increment-decrement, and modulus division operators.

2.11.7 | Bitwise Operators

Variables and constants are the basic data objects in any C program. Operators specify the operations to be performed on them. In addition, C provides low-level mechanisms to manipulate data at the level of individual bits. We now examine the bitwise operators that can be very helpful for writing system programs, which often have to manipulate individual bits.

A bit is a single BInary digiT - a zero ‘0’ or a one ‘1. Data in the machine is allocated an amount of storage and stored as a sequence of bits. For example, an int might be 16 bits long. The bitwise operators are applicable to integral data types only (int and char, with or without type modifiers). Real variables are also stored as a sequence of bits, but the bitwise operators do not apply to such data types.

  • Bitwise AND and OR: C provides six operations for bit manipulation of integral data types. The different bitwise operators and their meanings are given in Figure 2.8.

     

     

    Fig. 2.8 Bitwise Operators

     

    All bitwise operators are binary except for one's complement, which is unary. The shift operators have higher precedence than the relational operators. The bitwise AND and OR have higher precedence than the logical AND and OR.

    The bitwise AND operator yields a 1 if the corresponding bits in both values are 1; otherwise, it yields 0. The result of the bitwise operator OR is a 1 if either of the corresponding bits or both are 1; otherwise, it results in 0. Figure 2.9 shows the effect of the bitwise operators AND and OR on individual bits A and B.

     

     

    Fig. 2.9 (a) Effect of Bitwise AND on Individual Bits; (b) Effect of Bitwise OR on Individual Bits

     

    Now, considering three unsigned integer variables x, y, and z, where x = 29368 and y = 47357, the results of bitwise AND and bitwise OR are shown in Figure 2.10 for the operations:

     

     

    Fig. 2.10a Result of Bitwise AND on Two 16-bit Integers

     

     

    Fig. 2.10b Result of Bitwise AND on Two 16-bit Integers

     

           z = x & y; (Bitwise AND) 
           z = x | y; (Bitwise OR)

    In addition to the bitwise AND and OR operators, there is a shortcut AND operator ‘&=’, and a shortcut OR operator ‘|=’. The AND assignment equivalent and the OR assignment equivalent are given below:

           x = x & y; is equivalent to x &= y;
    and    x = x | y; is equivalent to x |= y;
  • Bitwise XOR: Moving on to the bitwise XOR operator, its effect when applied to two individual bits, A and B, is shown in Figure 2.11. The result of an XOR operation is 1 if only one of the corresponding bits is 1, and the other is a 0.

     

     

    Fig. 2.11 Effect of Bitwise XOR on Individual Bits

     

    Now, considering three unsigned integer variables x, y, and z, where x=29368 and y>47357, the results of bitwise ×OR are shown in Figure 2.12 for the operation:

     

     

    Fig. 2.12 Result of Bitwise XOR on Two 16-bit Integers

           z = ^ y; (Bitwise XOR)

    In addition to the conventional XOR operator, there is a shortcut XOR operator, ’^=’, the XOR and assignment equivalents of which are given below.

           x = x ^ y; is equivalent to x ^ = y;
  • Bitwise Shift Operators: The shift left ‘<<’ and the shift right ‘>>’ operators move the bits in the operand to the left and the right, respectively. These are binary operators. The operand to the left of the operator specifies the value to be shifted, while the operand to the right is an integral value specifying the number of bits to be shifted. The expression evaluates to the shifted integral value and leaves the operand unchanged. The general format of shift operations is:
           Variable << No of bits to be shifted     (left shift)
           Variable >> No of bits to be shifted     (right shift)

    For example, if n left shifts are specified on a variable x, then the result is an integral value with n bits shifted to the left, loss of the first n higher order bits, and introduction of n zeroes as new n lower order bits. The right shift, however, behaves in a different way. If the operand is unsigned or is non-negative, then one right shift is equivalent to division by 2; otherwise, the result is implementation-dependent. Most of the standard compilers like Microsoft Visual C++ compiler and the GNU C compiler perform signed shifts, i.e., the left bits are filled up by the signed bits. This is done to retain the meaning of division by 2.

    Let us consider the following statements where x and y are 16-bit integers, and x = 29368 and y = 3. The results are analysed in Figure 2.13.

     

     

    Fig. 2.13(a) Result of Left Shift on Two 16-bit Integers

     

     

    Fig. 2.13(b) Result of Right Shift on Two 16-bit Integers

           z = x << y; (shift left)
           z = x >> y; (shift right)

    When the shift operators are applied to signed integral values, the result ofa shift-right operation is unpredictable.

    In addition to the two basic shift operators, there are two shortcut shift operators, ‘<<=’ and ‘>>=’, the shift and assignment equivalents of which are given below:

           x = x << n; is equivalent to x <<= n;
    and    x = x >> n; is equivalent to x >>= n;
  • One's Complement Operator: This is a unary operator whose integral operand follows the operator. By definition, the one's complement operation performs a bitwise NOT on each bit in the operand. All ones are changed to zeroes, and all zeroes to ones. Its effect when applied to an individual bit, A, is shown in Figure 2.14.

 

 

Fig. 2.14 Effect of Bitwise NOT on an Individual Bit

 

Now, considering two integer variables x and z of16 bits, where x = 29368, the results of bitwise NOT (One's Complement) are shown in Figure 2.15 for the operation:

 

 

Fig. 2.15 Result of Bitwise One's Complement on a 16-bit Integer

       z = ~x; (bitwise one's complement)

Appendix B at the end of the book explains the use of bitwise operators through some examples of program codes.

2.11.8 | Ternary Conditional Operator

C includes a very special type of operator called the ternary conditional operator. Its basic syntax is:

       Expression1 ? Expression2 : Expression3;

If Expressionl is true, then the result of the whole expression is Expression2; otherwise, the result is Expression3. Both Expression2 and Expression3 must be of the same type. The following statements containing the ternary conditional operator illustrate the use of this special operator.

  • Max = (first >= second) ? first : second;

    Explanation: Finds the maximum of two numbers first and second, and stores the maximum value in Max

  • Odd = ((number % 2) != 0) ? 1 : 0;

    Explanation: Finds out if number is odd or even. If it is odd, then Odd=1; else, Odd=0

  • a = (a >= 0) ? a : -a;

    Explanation: Stores the absolute value of a in a

  • k = (i) ? (( j))?(i - j) :(j - i) (i + j);

    Explanation: Demonstrates nested use of the operator

2.11.9 | sizeof Operator

C provides the unary operator size of to find the number of bytes needed to store an object. It has the same precedence and associativity as the other unary operators. An expression of the form

       sizeof(object)

is an integer that represents the number of bytes needed to store the object in memory. This is done at compile time wherever possible. An object can be a basic data type such as int or float, or a user-defined data type such as a structure or a union, or it can be an expression. The following examples illustrate the use of this operator.

       a = sizeof (char);
       p = sizeof (a) * 8;

As stated earlier, storage space allocated might vary from machine to machine. However, in formal terms, it is guaranteed that

   sizeof(char)       = 1
   sizeof(short int) <= sizeof(int)         <= sizeof(long int)
   sizeof(signed int) = sizeof(unsigned int) = sizeof(int)
   sizeof(float)     <= sizeof(double)      <= sizeof(long double)
2.12 | OPERATOR PRECEDENCE

The relative precedence of the operators tells us which of the operators to apply first in an expression. For example, 3 + 5 * 4 should equal 32 or 23, depending on the order in which the computations are carried out. From the rules of arithmetic, we know that the correct result should be 23, since multiplication takes precedence over addition. On the other hand, (3+5)*4 produces the result 32, since the parentheses tells us to add 3 and 5 before multiplying the result by 4. The parentheses override the operator-precedence hierarchy, and thus have the highest precedence. The complete operator-precedence table is given in Figure 2.16, with operators on the same level having equal precedence and the precedence decreasing as we go down the table.

 

 

Fig. 2.16 Complete C Operator Precedence Table

 

As from the precedence table, we find that some operators have not been discussed. They are left for discussion in related chapters. The chapter on Pointers will deal with the → (arrow), * (indirection), and & (address of) operators. The ‘.’ will be presented when we learn about user-defined data types. The (type) operator will be dealt with in type casting, and the subscript operator when we learn about arrays.

2.13 | TYPE CONVERSION IN EXPRESSIONS

In C, variables in expressions with multiple data types undergo something called type promotion or upcasting. This signifies that variables of a ‘lower’ type are promoted (converted) to variables of the higher type appearing in the expression. In general, integral data types are ‘lower’ than floating-point types. More specifically, char is lower than int, which is lower than float; float is lower than double, and so on.

C can perform such conversions between different data types, but as a programmer, we should be careful about these conversions, since C permits operations that have implicit type conversions. However, it is important to note that the casting is a temporary step used only to aid expression evaluation. The data type of the casted variable (or expression) remains unchanged after the cast. This section describes the way that the conversions occur.

An arithmetic expression such as x + y has both a value and a type. For example, if x and y are both of the same type, say int, then x + y is also an int. However, if x and y are of different types, then the type of the result is guided by the following rules that is maintained in order.

  1. When all operands are of the same type, the result is of the same type as any of the operands.
  2. If either operand is a long double, then the other operand is automatically upcasted to a long double and the type of the result is also long double.
  3. If either operand is a double, then the other operands are upcasted to a double and the result is also a double.
  4. If either operand is a float, then the other operands are upcasted to a float and the result is also a float.
  5. When one operand is an unsigned long int, then the other operands are upcasted to an unsigned long int and that is the type of the result.
  6. If one of the operands is a long int, then the other operands are converted to long int and the result is of type long int.
  7. When one operand is unsigned and the other is of type int, the result is of unsigned type if no sign is present.
  8. If one of the operands is an int, and the other is of type short int char, then the short char is converted to an int and the result is of type int.

In general, type conversions do not cause a lot of trouble, but there is one possible pitfall one must watch out for. Mixing signed and unsigned quantities is fine until the signed number is negative.

2.14 | TYPE CONVERSION IN ASSIGNMENTS

For assignment statements, C converts the value at the right side of the assignment statement to the type of the variable on the left side (the LValue). Let us consider the following code segment:

       int a;
       char ch;
       float f;
       …
       ch = a;
       a = f;
       f = ch;
       f = a;

In the assignment ch = a, the lower order 8 bits of a are stored in ch. If the value of a was between 0 and 255, then ch and a would have identical values. In the statement a = f, a receives only the integer part of f. The third assignment converts the value of ch to its equivalent floating-point format. A similar cast also occurs in the last statement, where the value in a is converted to its floating-point counterpart, as dictated by the LValue f of type float.

2.15 | TYPE CASTING

The type of an expression can also be converted explicitly by type casting. The general form of a type cast is

       (Data Type) Expression

The above syntax is illustrated by the following example:

       int a = 8;
       float p = 5;
       …
       p = (float)(a) / p;

Explanation: Since a is converted to a float before the division operation, the division will be between two floats and the result will be returned as a float with value 1.6. If the explicit cast was not performed, the result would have been an int with value 1 and p would have been assigned the incorrect value 1.

2.16 | COMMENTS

Comments are written in C to enhance the readability of the program. Comments may be written to indicate the platform on which the program is written to run on, the author of the program, the purpose of the program, the purpose of a statement, or any other documentation purposes.

Comments can appear at any part of the program, even within statements. Commented portions are marked by a leading / * and a trailing */ called the comment delimiters. The compiler while generating the actual code for the source program ignores all text written within comment delimiters. Example comments might be:

       /* this is a comment */
       /* Date Created: 05-10-2004 */
       /* Comments are necessary in code */

Comments might be on a single line or might span multiple lines. In either case, delimiters must properly mark the commented portions.

       /* Comments are not necessarily single-lined; for example, this
       is a comment that spans multiple lines. Comments will
       always be ignored by the compiler; they are written for
       enhancing readability of the code. */

Sometimes, we use // for writing a single-line comment. It is to be noted that this is not strictly a feature of C, but of C++. Since we often use C++ compilers for writing C, this feature is widely available. This type of comments are marked by a leading // and the compiler treats it as a comment, the string following the delimiter until a new line is encountered, i.e., whatever follows the // on a line is a comment. This style of commenting code is useful for short explanatory comments for a single line of code. For example,

       int Total; //Variable used for storing the total gross salary

We cannot comment enough on the importance of comments in code. Properly commented code can also avoid wasting time in deciphering programs with lots of lines of code (LOC), among other important uses.

2.17 | FUNCTIONS REVISITED

As in the case with variables, functions in C are named so that they can be referenced using the name of the function. It must not coincide with a variable name or a reserved keyword. The naming conventions for functions are the same as those for variables, and the naming conventions suggested for identifiers in Section 2.9 are also suggested in the case of functions.

Without further hesitation, we give the general syntax of a function definition as:

       ReturnType FunctionName (Argument List) {
           (Body of the function)
           Definitions and Declarations;
           Statements;
       }

Any function consists of statements (body of the function) that describe the job done by the function and are enclosed in braces ‘{’,’}’. The opening brace indicates the start of the function and the closing brace indicates the end. Each statement in the body is terminated by a semicolon The statements in the body consist of definitions and declarations as we have discussed in Section 2.10, followed by other program statements (Refer to Section 1.8).

Now, let us return to our analogy of Section 2.8 with the above example with our definition of a function.

Every function in C has a name by which it is referenced. FunctionName is the name given to the function. A C program is made up of one or more functions, exactly one of which must be named main. Execution begins with main. It signifies the beginning of a C program.

Argument List is analogous to the list of groceries you took to the shopping function. The argument list enables the programmer to pass the values of certain variables to be used by the called function (in this case, the chore function) from the calling function (the watching home-video function). A function is invoked with zero or more arguments. The values of arguments, which represent information passed from the invoking to the invoked function, are obtained from expressions.

For a C program, the argument list is a list of argument definitions separated by commas, with only one argument in each definition, unlike multiple variable definitions as allowed in type definitions. The values passed by the invoking function are received into the defined arguments. Each argument as received by the invoked function has its own scope - the region of the program where it is visible. A variable is visible in a region if it can be referenced from that region. Variables defined inside a function are local to that function, i.e., variables defined within a function are visible only within that function. Local variables are not known to other functions besides their own.

The called function does some purposeful tasks and returns to the calling function. It may choose to return an indicative message (a value) to indicate success/failure or a result obtained to the calling function. The ReturnType gives the return type of the function, whether it returns an int value, a float or any other type. It may also choose to return nothing in the case where the ReturnType is void. The void return type is a special type, which is used as the return type of functions that do not return values. This is useful for functions that do, for instance, the tasks of output, where there is no need to return any value. The invoked function merely displays some information on the screen and returns to the calling function without returning anything. The void as a data type will be revisited in the section on Pointers, where we shall find out other uses for void.

As we know, a function is invoked from another function. The general syntax for such an invocation is:

       FunctionName (List of arguments passed);

Here, the FunctionName is referenced and a list of zero or more values, as indicated by the function definition, is passed to the invoked function. The above calling syntax can be used in any expression where the entire call is replaced by the return value and then the expression evaluated. However, since void functions do not return a value, they cannot be used in any expression.

As we know from variables, all functions, likewise, have to be declared before using them. Otherwise, there is no way of letting other functions know about the existence of a particular function.

The general syntax of a function prototype declaration in C is:

       ReturnType FunctionName (Argument Types List);

Argument Types List is a list of the data types of the Argument List as in the function definition. The other parameters retain the same meaning as in the function declaration.

We now move on to writing our first C program.

2.18 | PUTTING IT TOGETHER (FIRST C PROGRAM)

Without further hesitation, let us code our first working C program:

 

  Program Ch02n01.c

/*Program to add two integers*/

#include <stdio.h>

int main()
{
  int a=2,b=3,c;

  c=a+b;
  return 0;
}
End Program

Explanation: The program begins with a comment indicating the purpose of the program. Here, we have a single function named main. The function main accepts no values, and has a return type of integer (returns a value of type integer). The statements in main are enclosed in braces. The first statement declares three integers a, b and c of type int, and assigns 2 and 3 to a and b respectively. The next statement is an assignment, assigning the value of the expression (a + b) to c; thus, c has the value 5.

The final statement is a return statement specifying that the integer value returned by the function main is 0 (zero). This value is returned to the kernel of the operating system.

2.19 | SOME STANDARD LIBRARY I/O FUNCTIONS

This section explains in detail the use of some of the input/output functions in the standard library of C. All I/O in C is character-oriented. This includes writing and reading not only to and from the console, but also to the disc files as well. Console I/O operations in C are divided into two categories - unformatted console I/O functions and formatted I/O functions. Formatted I/O refers to the fact that these functions may format the information as per user's choice. The standard library provides functions of both categories. These functions are:

  • Unformatted console I/O operations:
    • getchar( )
    • putchar( )
    • gets()
    • puts( )
  • Formatted console I/O operations:
    • printf( )
    • sprintf( )
    • scanf( )
    • sscanf( )

In this section, we shall discuss printf( ) and scanf( ) functions. The others will be presented later as and when appropriate.

2.19.1 | The printf Library Function

The printf( ) function serves to write information to the screen. It allows a variable number of arguments, labels and sophisticated formatting of output. The general form of a call to the function is:

       printf(“Control String”, Optional List of Expressions);

The control string includes all the characters, escape characters and control specifiers required for desired output. The list of expressions is optional and if present, it prints all the values of the evaluated expressions in the order they are placed.

Consider the following program illustrating the printf function:

 

   Program Ch02n02.c

 /*Program to display a message*/
 #include <stdio.h>

 int main()
 {
   printf(“Hello readers.\n");
   printf(“This text is displayed,");
   printf(“ by a printf function.");
   return 0;
 }
 End Program
  EXECUTION

  Hello Readers.
  This text is displayed, by a printf function.

Explanation: The first line is a comment indicating the purpose of the program. The second line is the directive #include <stdio.h>. A semicolon does not terminate a directive. A # indicates a preprocessor directive, and the ‘include’ indicates that we wish to include a header file “stdio.h” (standard input-output) into our program. But we haven’t created any file named stdio.h. This is a standard C library header file available with all C compilers. It includes the prototypes for the library function printf, which we are about to use in our program. Exclusion of this file would result in an error in the terms of “Function printf not defined”. The first call to printf will cause the message string “Hello readers.” to be printed on the first line. Then, it will print a new line due to the \n escape character. The next printf function call, having passed the argument “This text is displayed,” will print the text on the new line. The next call to printf will print “by a printf function” without the double quotes, just after the previous output. No new line will be printed since there is no explicit mention of any.

The above example did not include any expression list. If a list is present whose values we want to print, printf has to perform substitutions in the text for the values of the corresponding expressions after evaluation. The control-string argument to printf may contain special character sequences beginning with a ‘%’, which serves as a placeholder for the value to be printed.

The different control specifiers are tabulated in Figure 2.17.

 

 

Fig. 2.17 Control Specifiers in Control String of Printf

 

The two additional modifiers ‘h’ and T can be used with the placeholders to specify more types. A few examples of control specifiers with additional modifiers as placeholders of the other data types are shown in Figure 2.18.

 

 

Fig. 2.18 Additional Control Specifiers in Control String of printf

 

Consider the following program:

 

  Program Ch02n03.c

/*Program to display a message with expression arguments in the printf call*/
#include <stdio.h>
int main()
{
 printf(“Get Set: %s, %d, %f %c%c \n","one",2,3.33,‘G’,‘O’);
 return 0;
}
End Program
  EXECUTION
    Get Set: one, 2, 3.330000 GO

Explanation: The first argument is the control string. The placeholders (formats) in this string are matched with other arguments in the list. Hence, the %s corresponds to “one”, the %d corresponds to 2, the %f to 3.33, the first %c to ‘G’ and the second %c to ‘O’. Each format in the control string specifies how the value of its corresponding argument is to be printed. The ‘\n’ at the end indicates that we want a new line to be printed after printing the output.

When an argument is printed, the place where it is printed is called its field and the number of characters, its field width. The field width can be specified explicitly by an integer occurring between the % and the rest of the placeholder format. Let us look at an example.

 

  Program Ch02n04.c

 /*Program to display a message with expression arguments and field
 width specifiers in the printf call*/
 #include <stdio.h>

 int main()
 {
   printf(‘%c%3c%7c\n’,‘A’,‘B’,‘C’);
   return 0;
 }
 End Program
  EXECUTION ( represents a blank)

  

Explanation: First, the letter ‘A’ is printed. Then, the letter ‘B’ is printed with a field width of three characters. Since the letter ‘B’ requires a width of exactly one, the other two spaces are blanks. Then, the letter ‘C’ is printed with a field width of seven characters, and six trailing blanks appear after ‘C’ is printed due to reason similar to that in the case of ‘B’.

Additional modifiers are available for specifying precision. The field width followed by a point ‘.’ and the precision value as an integral constant specify the number of places after decimal. It is, however, important to note that when the argument is longer than the field width in the placeholder, the printed value is truncated and in some cases, rounded. For example, if x is an unsigned integer containing 128764 and the format is %3d, then only 128 is printed (truncation). However, if y is a float containing 129.546 and the control string is %7.2F, then it prints 129.55 as a rounded result. Sometimes, type conversion can be interesting. Consider the following program.

 

  Program Ch02n05.c

#include <stdio.h>

int main()
{
  char ch=‘0’;
  int n=50;

  printf(“%c,%d,%c,%d,%o,%x\n",ch,ch,n,n,ch,n);
  return 0;
}
End Program
  EXECUTION (0 represents a blank)
  0,48,2,50,60,32

Explanation: First, character ‘0’ prints as itself. Next, since we want to print the character ch as an integer, the ASCII value of ‘0’, i.e., 48 is printed. The next value printed is 2, which is the character whose ASCII value is 50. Then, n prints its value as 50. The next format string %o prints the octal representation of 48, i.e., 60. The hexadecimal representation of 50 is 32. So, the integer 32 is printed at the end.

Usually, the output printed by usage of field-width specifiers if the specifier is larger than the actual width of the value, is right justification with leading blanks added to the value to account for the difference. To achieve left justification in output, we add a minus ‘—’ after the % in the placeholder. Additionally, if we want to print all positive numbers with a leading + sign and all negative numbers with a leading - sign, then a plus ‘+’ sign is used after the %. Without this flag, only negative items are preceded by the minus sign. Further, while using right justification for numeric values, a ‘0’ (zero) after the % causes leading zeroes to appear instead of leading blanks. If a # is used as the flag along with o and x control characters, the corresponding octal and hexadecimal numbers will be preceded by a 0 and a 0x respectively. Try experimenting with as many possibilities as possible.

It is important to note that the expression arguments are evaluated from right to left. An interesting situation possible is:

 

  Program Ch02n06.c

#include <stdio.h>

int main()
{
  int a=10;

  printf(“%d,%d,%d,%d\n",a++,++a,a--,--a);
  printf(“%d\n",a);
  return 0;
}
End Program
  EXECUTION

  9,9,9,9
  10

Explanation: Since function arguments are evaluated from right to left, at first, the rightmost argument --a evaluates to 9, and 9 is placed at the final %d placeholder. Then, a-- is evaluated to 8, but since in the postfix form the initial value is sent and then the variable updated with the new value, the second placeholder %d will have the value 9. For similar reasons, ++a prints the incremented value of a, i.e., 9 and for the first placeholder (the first ‘%d’) a++ is evaluated to 10, but the value before increment, i.e., 9, is sent. Thus, the placeholders in order will print 9, 9, 9 and 9, respectively. The second printf statement prints the current value of a, i.e. 10.

The printf function has an integer return value (i.e. it returns an integer to the calling function). The return value is -1 if an error has occurred during processing of the printf function. The return value can thus be checked to detect whether the printf statement has executed successfully or not.

2.19.2 | The scanf Library Function

It is a general input function also defined within the standard library header file stdio.h. The general form of this function is:

       scanf(“Control String",&Variable1<,&Variablei>);

The control string contains the format of the data being received. As before, the items in angular brackets ‘<’ and ‘>’ signify terms that can be repeated. The ‘<’ (ampersand) before each variable name is an operator that specifies the memory address of the variable. Thus, an address is passed to the scanf function for each variable in the list. The ampersand operator is read as ‘address of. The purpose of such a parameter will become clear when we read on Pointers.

As with printf, different control placeholders are available for scanf to be used in the control string. They are the same as those tabulated for printf in Figure 2.17 and Figure 2.18. The %s is used for scanning (reading) a string and this will be dealt with later on in the chapter on Arrays.

The function scanf reads data from the standard input (stdin), usually the keyboard. The data must be entered in the specified format and on input; scanf stores the data at the specified memory locations (corresponding to the variables in the list). The following program uses scanf( ) to read in values from the user.

 

  Program Ch02n07.c

/*Program to demonstrate scanf*/
#include <stdio.h>

int main()
{
  float Average;
  int Count;

  printf(“Enter the values for Average and Count,\n");
  printf(“Separated by spaces or newline: \n");
  scanf(“%f%d",&Average,&Count);
  printf(“\nAverage=%f, Count=%d.\n",Average,Count);
  return 0;
}
End Program
  EXECUTION (Boldface represents user input)

  Enter the values for Average and Count,
  Separated by spaces or newline:
  23.25 12
  Average=23.250000, Count=12.

It is important to distinguish between whitespace and non-whitespace characters while dealing with scanf. In effect, the whitespace appearing in the control string is ignored. Whitespaces in the variable list are also ignored. However, if the format %c is used, then a blank space is considered a valid character. Consider the following example:

 

  Program Ch02n08.c

#include <stdio.h>

int main()
{
  int a,b;
  char c;

  scanf(“%2d%c%d",&a,&c,&b);
  printf(“\n%d,%d,%d\n",a,c,b);
  return 0;
}
End Program
  EXECUTION (Boldface represents user input)

  15 6
  15, 32, 6

Here, the character ‘c’ has a blank as its value.

We can use whitespace to separate the individual items of information in the input, but we cannot use other characters (commas, semicolons, dashes, etc.) to separate them unless we include those characters at the appropriate places in the control string. Consider the following example, which will use the comma as a separator between inputs.

 

  Program Ch02n09.c

#include <stdio.h>

int main()
{
  int x,y,z;

  scanf(“%d,%d,%d",&x,&y,&z);
  printf(“\n%d\n%d\n%d\n",x,y,z);
  return 0;
}
End Program

  EXECUTION (Boldface represents user input)

  4,7,19
  4
  7
  19

As we had for printf, scanf can also have field widths for inputs in the control string. These are interpreted as the maximum number of places to read for that variable. The scanf function will literally stop in mid-word or mid-number if we specify a field width smaller than the value entered. Moreover, the function will continue processing from the next character. The following program will illustrate this.

 

  Program Ch02n10.c

#include <stdio.h>

int main()
{
  int b1,b2;
  char c1;

  scanf(“%1d %c %d",&b1,&c1,&b2);
  printf(“\nb1=%d\nc1=%c\nb2=%d\n",b1,c1,b2);
  return 0;
}
End Program

   EXECUTION (Boldface represents user input)
 
   123 C 45
   b1=1
   c1=2
   b2=3

Explanation: Since the variable bl was specified to have a field width of one place, so bl stores the first character entered, i.e. 1. Next, the control format for cl being %c, cl stores 2 as a character. The scanf function now reads right after the last character (in this case, 2) has been processed, and continues reading into the next variable until it reaches a valid separator (in this case, a blank space). Since scanf finds 3, it assigns this to the variable b2. The other two inputs are ignored.

There are often instances where one wishes to read only some of the data items that appear on a line of the input. A special character, the asterisk (*), is used to indicate that a field is to be ignored or skipped. The folloing program listing illustrates this:

 

  Program Ch02n11.c

#include <stdio.h>

int main()
{
  int b1,b2;
  scanf(“%d%*d%d",&b1,&b2);
  printf(“\nb1=%d\nb2=%d\n",b1,b2);
  return 0;
}
End Program

   EXECUTION (Boldface represents user input)

   1 2 3
   b1=1
   b2=3

Explanation: The above program reads three integer values. It processes the first and assigns it to bl. The program reads the second integer but discards it as instructed by the asterisk (*) preceding the format specifier in the second placeholder. The program then processes the third integer and stores it in b2.

As with printf, scanf returns a value, which gives the number of arguments it has successfully processed, or a value of -1 if an error has occurred. During processing of the input, this return value can be used by the programmer to detect errors, if any.

2.19.3 | The getchar Library Function

The getchar function returns a single character typed in from the standard input device. It is another general input function defined within the standard library header file stdio.h. The general form of this function is:

       getchar();

The function accepts no arguments. It returns the ASCII value of the key that was pressed on success after converting it to an int without sign extension. On error, the function returns the character EOF. Hence, if ‘B’ is pressed during a successful call to getchar( ), the function returns the ASCII value of B, which is 66. We normally use the getchar function as shown in the following skeleton code:

int ch;
       …
       ch=getchar();
       …

It might seem strange that we choose to define the variable (into which we assign the value returned by getchar) as an integer. The return value can be accepted into a char without any error. However, there are other reasons for defining it to be an int. The reason most often cited is that the getchar function can return any valid character. Since the normal default for an end-of-file marker is –1, if the char data type is used, the value of—1 may never be returned.

The getchar function may be called successively to read characters from a line of text. The following example reads in characters one after the other until the Enter key is pressed.

 

  Program Ch02n12.c

/*Program to read in characters until enter is pressed
*It uses getchar function*/
#include <stdio.h>

int main()
{
  int ch=‘ ’;

  while(ch!=‘\n’)
     ch=getchar();
  return 0;
}
End Program

The program is self-explanatory. The use of getchar( ) will be clear as we use it in future programs.

2.19.4 | The putchar Library Function

It can be called the complement of the getchar function. The putchar library function is defined in the standard C header stdio.h and it displays a character on the standard output device. The general form of this function is:

       putchar(Integer_Expression);

The integer expression passed to putchar( ) should be a character. The character passed is displayed at the terminal. This function returns the character passed as an integer on success. On error, putchar returns the special character EOF. The following example program demonstrates the use of putchar( ) to write characters to the terminal.

 

  Program Ch02n13.c

/*Program to display characters using the putchar function*/
#include <stdio.h>

int main()
{
  int c1=97,c2=100;

  putchar(c1);
  if(putchar(c2)==EOF)
      printf(“error");
  return 0;
}
End Program

The above program is self-explanatory.

The next program uses the putchar function to print all lowercase letters.

 

  Program Ch02n14.c

/*Program to print all lowercase letters*/
#include <stdio.h>

int main()
{
  int i,ch;

  for(i=0;i<26;i++)
  {
    ch=i + ‘a’;
    putchar(ch);
  }
  return 0;
}
End Program

The use of putchar( ) will become clear as we use it in future programs. It is left to the reader to analyse the function further.

2.20 | SCOPE OF IDENTIFIERS

C recognizes identifiers (such as those for variables and functions) as valid only in certain parts of a program. Since identifiers are stored in memory, a memory location may be accessible only in certain functions. The rules of scope and visibility determine the rules for accessing and referring to variables. The scope of an identifier is the range of contexts in which the identifier can be used. The visibility of an identifier is the range of contexts over which the identifier is valid.

A compound statement is a series of declarations followed by a series of statements enclosed within braces (’{’ and ‘}’). Its main purpose is to group statements into an executable unit. A compound statement is interchangeably called a block when variable definitions are present.

Here is a simple example of a block.

 

  Program Ch02n15.c

#include <stdio.h>

int main()
{
  int a=2;
  printf(“%d\n",a);        /*2 is printed*/
  {                        /* Start of Inner block*/
    int b=3;
    printf(“%d\n",b);      /*3 is printed*/
  }                        /* End of Inner block*/
  return 0;
}
End Program

  EXECUTION

  2
  3

The main purpose of blocks is to allow for variables to be created where needed. If memory is a scarce resource, then a block exit will release the storage allocated locally to the block (in the above code, the variable b), allowing the memory to be used for some other purpose. Functions can be viewed as named blocks with parameters and return statements allowed.

The basic rules of scoping is that identifiers are accessible only within the block in which they are defined. They are unknown outside the boundaries of that block. This situation permits use of the same identifier in different definitions. Now, the problem is to identify the object that the identifier refers to. An example will clarify this situation.

 

  Program Ch02n16.c

#include <stdio.h>

int main()
{
  int a=2;                      /*Declaration in Outer Block*/
  printf(“%d\n",a);             /*2 is printed*/
  {
    float a=3.25,c=2.1;         /*Inner block declaration*/
    printf(“%f\n",a);           /*3.25 is printed*/
  }
  printf(“%d\n",a);             /*2 is printed*/
  return 0;
}

End Program

   EXECUTION

   2
   3.250000
   2

Explanation: Here, the float variable a defined in the inner block is not visible when the control reaches the second printf statement in the outer block.

Each block introduces its own catalogue of names. A name in the outer block is valid in an inner block unless the inner block chooses to redefine it. If redefined, the outer block name is hidden, or masked from the inner block. Inner blocks may be nested to arbitrary depths, constrained only by system limitations.

When a block is entered, the system sets aside adequate memory for the automatically declared variables local to the block. When the block ‘exits’, the system no longer reserves the memory set aside for the automatic variables. As a result, these values are lost. If the block is re-entered, the storage is once again appropriately allocated, but previous values remain unknown. Thus, each invocation of a block sets up a new environment.

Let us look at the variable a. This variable is defined in the functional body of main( ). This variable is called a local variable. Local variables are those defined within a function or a block, as opposed to the global variables, which are defined outside any function. All variables defined outside function bodies are global variables. When a variable is defined outside any function, storage is permanently assigned to it and it becomes an external variable. A definition for an external variable can look the same as that for a variable definition occurring inside a function or a block. Such a variable is considered global to all functions defined after it and exists between different function calls.

 

  Program Ch02n17.c

#include <stdio.h>
/*Global variable definition outside any function*/
int a=2;

void f()                       /*A function*/
{
  printf(“%d\n",a);            /*prints global variable as 3*/
}
int main()
{
  printf(“%d\n",a);            /*2 is printed*/
  {
	float a = 3.25;        /*Inner block definition*/
	printf(“%f\n",a);      /*3.25 is printed*/
  }
  printf(“%d\n",a);            /*2 is printed*/
  a++;                         /*a is now 3*/
  f();
  printf(“%d\n",a++);          /*3 is printed*/
  return 0;
}
End Program

   EXECUTION

   2
   3.250000
   2
   3
   3

Explanation: In the above program, a is a global variable and it is available in main( ) as well as in f( ).

2.21 | STORAGE CLASSES

The lifetime of a variable specifies how long the variable's value will be around. Ordinarily, the values for local variable will simply disappear after the function or block exits. Sometimes, however, it can be useful to keep a value around, for the next time the function is called. C's storage-class specifiers give us the control to instruct the compiler over how and where a variable is to be stored when the program is executing. The four storage-class specifiers available in C are automatic, static, register and external, with four corresponding keywords, auto, static, register and extern, which can be used before variable types in the variable definition.

2.21.1 | Automatic Storage Class

Variables defined inside of function bodies are automatic by default. Thus, automatic is the most common of the four storage classes. This specifier can only be used in definition at the top of a block before other statements are written. A global variable cannot be specified with auto storage. The definition

     void f(){      /*A function*/
       int a;
       char c,d,e;
       …
     }

is equivalent to


     void f(){                     /*A function*/
	auto int a;           /*Automatic variable definitions*/
	auto char c,d,e;
	…
     }

It is evident from the above example that if we do not include a storage-class specifier for a local variable, the compiler assumes the auto storage by default. For this, we shall rarely use the auto specifier.

By default, an automatic variable has an unpredictable or a garbage value as its initial value. It is stored in memory and its scope and lifetime are restricted as local to the block in which the variable is defined. Automatic variables defined inside a block cannot be referenced outside the block, and the variable persists as long as control remains within the block. Since automatic variables are initialized to garbage values, they should be explicitly initialized.

 

  Program Ch02n18.c

#include <stdio.h>

int main()
{
  auto int a,b=15;    /*Automatic variables*/

  /*Prints a=garbage value and b=15*/
  printf(“a=%d,b=%d\n",a,b);
  return 0;
}
End Program

2.21.2 | Register Storage Class

The register storage-class specifier also specifies automatic storage duration. In addition, it suggests to the compiler that the variable be stored in a machine register for faster access.

Generally, all integer type variables such as char and int can be given register storage.

Using the register keyword with other data types result in the compiler ignoring the register keyword and treating the variable as an automatic variable.

An example might be:

 

  Program Ch02n19.c

#include <stdio.h>
 
int main()
{
  register int i=10;

  printf(“%d\n”, i);
  return 0;
}
End Program

It is important to note that the register keyword is only a request, not a command to the compiler. If the compiler is unable to comply with the request (due to lack of free registers, etc.), the variable becomes an automatic variable.

Similar to automatic variables, register variables are not automatically initialized. Scope and lifetime rules are the same as those for automatic variables.

2.21.3 | Static Storage Class

This storage-class specifier makes it possible to define a local variable whose value persists between different function calls - i.e., the most recent value of the variable can be reused the next time the function is called. In other words, it allows a local variable to retain its previous value upon re-entry into a block. This is in contrast to auto and register variables that lose their value upon block exit. An automatic variable would have behaved as in the following:

 

  Program Ch02n20.c

#include <stdio.h>

void f1()
{
  auto int count=0;     /*Automatic variable declaration*/

  printf(“count=%d\n",++count);
}

int main()
{
  f1();     /*prints 1*/
  f1();     /*prints 1*/
  f1();     /*prints 1*/
  return 0;
}
End Program
EXECUTION

   count=1
   count=1
   count=1

On the other hand, the behavior in case of static storage-class specifier is as follows:

 

  Program Ch02n21.c

#include <stdio.h>

void f1()
{
  static int count=0;    /*Static variable declaration*/

  printf(“count=%d\n",++count);
}
int main()
{
  f1();     /*prints 1*/
  f1();     /*prints 2*/
  f1();     /*prints 3*/
  return 0;
}
End Program

   EXECUTION

   count=1
   count=2
   count=3

Explanation: The variable count in function f( ) is initialized to 0 only once. Whenever the function is called, the old value is retained, and then the old value is incremented by 1 and printed. As in the above listing, a static variable count can be used to find the number of times a function is called during execution of a program.

C automatically provides initialization to zero for static-class variables. Hence, we could have omitted the initialization part in the static declaration for count as the variable would have been automatically initialized to zero. Local scope and a lifetime equal to the lifetime of the program are the features of a static storage-class variable.

We can also use the static storage-class specifier with global variables. Static variables should not be overused, especially where memory is scarce, since they are always kept in memory throughout the lifetime of the program.

2.21.4 | External Storage Class

All global variables and functions have external storage class by default. A definition for an external variable can look the same as that for a variable definition occurring inside a function or a block. Such a variable is considered to be global to all functions defined after it, and the external variables persist among different function calls, i.e., the lifetime of extern variables is throughout the program. Extern variables are demonstrated below. The variable vl is defined after f( ) and main( ), but is used in both. If we have to use the variable, it results in an error in the terms of “Variable vl not defined,” since vl has been globally defined after both functions and, thus, is not visible to either.

     void f(){
       vl = 5;  f*Error, vl is not found*/
     }
     int main(){
       f();
       printf(“%d\n”, v1);
       return 0;
     }
     int vl;

However, if vl was declared as extern in both functions, then both will refer to the global integer vl. This is in contrast to declaring vl as automatic integers in the functions where new variables named vl would be created as locals and the purpose of treating vl as a global variable would be defeated.

Extern variables never disappear. Since they can exist throughout the execution life of the program, they can be used to transmit values across functions. Of course, an extern variable may be hidden if redefined within a local block.

 

  Program Ch02n22.c

#include <stdio.h>

void f()
{
  extern int v1;

  v1=5;
}

int main()
{
  extern int v1;
  f();
  printf(“%d\n",v1);    /*Prints 5*/
  return 0;
}

int v1;
End Program
2.22 | STORAGE TYPE QUALIFIERS

The two storage type qualifiers const and volatile control how the variable is modified and accessed.

2.22.1 | const

Variables declared to be of type const cannot be modified at a later point in the program. However, they can be given initial values as:

       const int i=22;

This type qualifier protects a variable from being modified by a program or a function. However, external events such as hardware actions may change the value of a const variable. The qualifier is especially important in a function-parameter declaration. If the parameters in the function are declared as const, it is assured that the function cannot (even accidentally) change the input parameter (if it is a pointer). This technique is shown below:

       void f(const char *str){
         …
       }

       int main()
         …

       }

2.22.2 | volatile

Since this section requires some basic knowledge about control statements to understand the example provided, it is recommended that the reader who is studying C for the first time skip this part without hesitation. He/she can come back to this section after reading the chapter on Control.

If a variable is declared as volatile, it informs the compiler that the value of the variable may change due to factors other than those coded in the program. For example, some device or the operating system may change the value of a variable in memory externally, without changes being present in the program code. This phenomenon becomes a problem when it comes to optimizing compilers, which shuffle and change bits and pieces of the code around to make it shorter and/or execute faster. Thus, given the following code fragment:

       a=10;
       t=0;
       while(a){
          t++;
       }

since the variable a is not modified anywhere inside the while construct (because a is not present on the left side of any assignment statement), the compiler may safely optimize the code to look like:

       a=10;
       t=0;
       while(1){
          t++;
       }

The above optimized code would result in the absence of a condition check at the onset of each execution of the while loop construct. Thus, our optimizing compiler will wisely create an equivalent code that executes faster than the original one.

All is fine if we consider that the variable a cannot be modified within the while loop construct. However, with multiple processes and even I/O devices being allowed to access memory, it may so happen that our program might be waiting for an external event to modify the value at the address associated with a. In such a situation, the optimization is a problem and needs to be bypassed. The keyword volatile, if present in a variable declaration, does exactly the same. It forces a check for the value of the variable each time its value is accessed - even if it is not modified between two reads. For example, the following declaration will create a volatile integer.

       volatile int a;

Both the qualifiers can be used together, and the effect would be a variable that can be changed by external events only.

SUMMARY

A C program consists of creating variables of the data types available and manipulating the variables using constants, operators, and other variables to form expressions. Variables are stored in the primary memory, also called the RAM. C is a case-sensitive language. There are 32 keywords in C, with special meanings. Whitespaces may be freely used with some restrictions to improve the readability of code.

Variables and constants belong to certain domains or data types. There may be integral, floating point, or character data types with other modifications. Floating-point data can be of single, double, or extended precision as the requirement is. Strings are collections of characters. Operators in C are used along with operands to form expressions. Operators have their precedence (giving the order in which different operators are to be evaluated in an expression) and associativity (giving the evaluation order for the same operator). A value may be stored into an LValue, also called a memory location. In addition, different data types may be cast to other data types.

C code is made readable by introducing comments as and when required. The C compiler ignores comments. To facilitate input and output, C provides formatted and unformatted I/O functions. The printf() is a standard output function declared within stdio.h. It accepts a format string and prints the string. There may be placeholders in the string to enable printing of certain values from expressions. Field-width specifiers specify the width of the printed field. The scanf() is a standard input function declared in stdio.h. It takes input from the standard input (keyboard) into program variables.

The rules of scope and visibility determine the access to variables in a program. Accordingly, there may be local and global variables. Compound statements are a group of statements logically grouped together.

The storage class of a variable further determines the rules of scope, visibility and storage. There are four storage classes — automatic, register, static and external.

NEW TERMINOLOGY CHECKLIST
  • Memory
  • RAM
  • Case-Sensitive
  • Keyword
  • Whitespaces
  • Data
  • Domains
  • Data-Types
  • Single Precision
  • Double Precision
  • Extended Precision
  • Strings
  • Variables
  • Identifiers
  • Functions
  • Operator
  • Operand
  • Expression
  • Precedence
  • Associativity
  • L-Value
  • R-Value
  • Type Casting
  • Comments
  • Placeholder
  • Field-width
  • Scope
  • Visibility
  • Compound Statements
  • Local Variables
  • Global Variables
  • Storage Classes
  • Automatic
  • Register
  • Static
  • External
EXERCISES
  1. How many types of datas are available in C?
  2. What is qualifier or modifier of data type?
  3. What is the size of each data type?
  4. What is const and volatile qualifier?
  5. What is the meaning of declaration?
  6. What is an operator precedence?
  7. What is type casting in C language?
  8. When should a type cast be used?
  9. Write a C++ program which reads values for two floats and outputs their sum, product, and quotient. Include a sensible input prompt and informative output
  10. To what do the following expressions evaluate? 17/3 17%3 1/2