0% found this document useful (0 votes)
9 views

CD_UNIT III

Uploaded by

Moshika Vetrivel
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

CD_UNIT III

Uploaded by

Moshika Vetrivel
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 69

UNIT 3 SEMANTIC ANALYSIS AND SYMBOL

TABLE
• Attribute grammars, syntax directed definition, evaluation and flow of attribute
in a syntax tree Basic structure, symbol attributes and management. Run-time
environment: Procedure activation, parameter passing, value return, memory
allocation, scope.
Attribute grammars:
• Attribute grammar is a special form of context-free grammar where some additional
information (attributes) are appended to one or more of its non-terminals in order to provide
context-sensitive information. Each attribute has well-defined domain of values, such as
integer, float, character, string, and expressions.
• Attribute grammar is a medium to provide semantics to the context-free grammar and it can
help specify the syntax and semantics of a programming language. Attribute grammar (when
viewed as a parse-tree) can pass values or information among the nodes of a tree.
• Example
• E → E + T { E.value = E.value + T.value }
• The right part of the CFG contains the semantic rules that specify how the grammar should be
interpreted. Here, the values of non-terminals E and T are added together and the result is
copied to the non-terminal E.
• Semantic attributes may be assigned to their values from their domain at the time of parsing
and evaluated at the time of assignment or conditions. Based on the way the attributes get
their values, they can be broadly divided into two categories : synthesized attributes and
inherited attributes.
syntax directed definition:
Prerequisite – Introduction to Syntax Analysis, Syntax Directed Translation
Syntax Directed Definition (SDD) is a kind of abstract specification. It is generalization of context free
grammar in which each grammar production X –> a is associated with it a set of production rules of the
form s = f(b1, b2, ……bk) where s is the attribute obtained from function f. The attribute can be a string,
number, type or a memory location. Semantic rules are fragments of code which are embedded usually at
the end of production and enclosed in curly braces ({ }). Example:
E --> E1 + T { E.val = E1.val + T.val}
Annotated Parse Tree – The parse tree containing the values of attributes at each node for given input
string is called annotated or decorated parse tree.
Features –
•High level specification
•Hides implementation details
•Explicit order of evaluation is not specified
Types of attributes – There are two types of attributes:
1. Synthesized Attributes – These are those attributes which derive their values from their children
nodes i.e. value of synthesized attribute at node is computed from the values of attributes at children
nodes in parse tree.
Example:
E --> E1 + T { E.val = E1.val + T.val}
In this, E.val derive its values from E1.val and T.val
Computation of Synthesized Attributes –
•Write the SDD using appropriate semantic rules for each production in given grammar.
•The annotated parse tree is generated and attribute values are computed in bottom up manner.
•The value obtained at root node is the final output.
Example: Consider the following grammar
S --> E E --> E1 + T E --> T T --> T1 * F T --> F F --> digit
The SDD for the above grammar can be written as follow

Let us assume an input string 4 * 5 + 6 for computing synthesized attributes. The annotated parse tree for the input
string is
For computation of attributes we start from leftmost bottom node. The rule F –> digit is used to reduce digit to F and
the value of digit is obtained from lexical analyzer which becomes value of F i.e. from semantic action F.val = digit.lexval.
Hence, F.val = 4 and since T is parent node of F so, we get T.val = 4 from semantic action T.val = F.val. Then, for T –> T1 *
F production, the corresponding semantic action is T.val = T1.val * F.val . Hence, T.val = 4 * 5 = 20
Similarly, combination of E1.val + T.val becomes E.val i.e. E.val = E1.val + T.val = 26. Then, the production S –> E is
applied to reduce E.val = 26 and semantic action associated with it prints the result E.val . Hence, the output will be 26.

2. Inherited Attributes – These are the attributes which derive their values from their parent or sibling nodes i.e. value of
inherited attributes are computed by value of parent or sibling nodes.
Example:
A --> BCD { C.in = A.in, C.type = B.type }

Computation of Inherited Attributes :


Construct the SDD using semantic actions.
The annotated parse tree is generated and attribute values are computed in top down manner.
Example: Consider the following grammar
S --> T L
T --> int
T --> float
T --> double
L --> L1, id
L --> id
The SDD for the above grammar can be written as follow
Let us assume an input string int a, c for computing inherited attributes. The annotated parse tree for the input

string is
The value of L nodes is obtained from T.type (sibling) which is basically lexical value obtained as int, float or
double. Then L node gives type of identifiers a and c. The computation of type is done in top down manner or
preorder traversal. Using function Enter_type the type of identifiers a and c is inserted in symbol table at
corresponding id.entry.
Evaluation and flow of attribute in a syntax tree
Basic structure
• A syntax tree is a tree in which each leaf node represents an operand, while each inside
node represents an operator. The Parse Tree is abbreviated as the syntax tree. The syntax
tree is usually used when representing a program in a tree structure.

• Rules of Constructing a Syntax Tree


• A syntax tree’s nodes can all be performed as data with numerous fields. One element of
the node for an operator identifies the operator, while the remaining field contains a
pointer to the operand nodes. The operator is also known as the node’s label. The nodes of
the syntax tree for expressions with binary operators are created using the following
functions. Each function returns a reference to the node that was most recently created.

• 1. mknode (op, left, right): It creates an operator node with the name op and two fields,
containing left and right pointers.
• 2. mkleaf (id, entry): It creates an identifier node with the label id and the entry field, which
is a reference to the identifier’s symbol table entry.
3. mkleaf (num, val): It creates a number node with the name num and a field containing the number’s value, val. Make a
syntax tree for the expression a 4 + c, for example. p1, p2,…, p5 are pointers to the symbol table entries for identifiers ‘a’ and
‘c’, respectively, in this sequence.

Example 1: Syntax Tree for the string a – b c + d is:


Example 2: Syntax Tree for the string a * (b + c) – d /2 is:

Variants of syntax tree:


A syntax tree basically has two variants which are described below:
Directed Acyclic Graphs for Expressions (DAG)
The Value-Number Method for Constructing DAGs
Directed Acyclic Graphs for Expressions (DAG)
A DAG, like an expression’s syntax tree, includes leaves that correspond to atomic operands and inside codes that
correspond to operators. If N denotes a common subexpression, a node N in a DAG has many parents; in a syntax
tree, the tree for the common subexpression would be duplicated as many times as the subexpression appears in
the original expression. As a result, a DAG not only encodes expressions more concisely but also provides
essential information to the compiler about how to generate efficient code to evaluate the expressions.

The Directed Acyclic Graph (DAG) is a tool that shows the structure of fundamental blocks, allows you to examine
the flow of values between them, and also allows you to optimize them. DAG allows for simple transformations of
fundamental pieces.

Properties of DAG are:

Leaf nodes represent identifiers, names, or constants.


Interior nodes represent operators.
Interior nodes also represent the results of expressions or the identifiers/name where the values are to be stored
or assigned.
Examples:
T0 = a+b --- Expression 1
T1 = T0 +c --- Expression 2
Expression 1: T0 = a+b
Expression 2: T1 = T0 +c The Value-Number Method for Constructing DAGs:

An array of records is used to hold the nodes of a syntax tree or DAG. Each row of the array corresponds to a
single record, and hence a single node. The first field in each record is an operation code, which indicates the
node’s label. In the given figure below, Interior nodes contain two more fields denoting the left and right
children, while leaves have one additional field that stores the lexical value (either a symbol-table pointer or a
constant in this instance).
he integer index of the record for that node inside the array is used to refer to nodes in this array. This integer has
been referred to as the node’s value number or the expression represented by the node in the past. The value of
the node labeled -I- is 3, while the values of its left and right children are 1 and 2, respectively. Instead of integer
indexes, we may use pointers to records or references to objects in practice, but the reference to a node would
still be referred to as its “value number.” Value numbers can assist us in constructing expressions if they are
stored in the right data format.

Algorithm: The value-number method for constructing the nodes of a Directed Acyclic Graph.
INPUT: Label op, node /, and node r.
OUTPUT: The value number of a node in the array with signature (op, l,r).
METHOD: Search the array for node M with label op, left child I, and right child r. If there is such a node, return
the value number of M. If not, create in the array a new node N with label op, left child I, and right child r, and
return its value number.
While Algorithm produces the intended result, examining the full array every time one node is requested is
timeconsuming, especially if the array contains expressions from an entire program. A hash table, in which the
nodes are divided into “buckets,” each of which generally contains only a few nodes, is a more efficient method.
The hash table is one of numerous data structures that may effectively support dictionaries. 1 A dictionary is a
data type that allows us to add and remove elements from a set, as well as to detect if a particular element is
present in the set. A good dictionary data structure, such as a hash table, executes each of these operations in a
constant or near-constant amount of time, regardless of the size of the set.

To build a hash table for the nodes of a DAG, we require a hash function h that computes the bucket index for a
signature (op, I, r) in such a manner that the signatures are distributed across buckets and no one bucket gets
more than a fair portion of the nodes. The bucket index h(op, I, r) is deterministically computed from the op, I,
and r, allowing us to repeat the calculation and always arrive at the same bucket index per node (op, I, r).

The buckets can be implemented as linked lists, as in the given figure. The bucket headers are stored in an array
indexed by the hash value, each of which corresponds to the first cell of a list. Each column in a bucket’s linked list
contains the value number of one of the nodes that hash to that bucket. That is, node (op,l,r) may be located on
the array’s list whose header is at index h(op,l,r).
symbol attributes:
• Symbol table is an important data structure created and maintained by compilers in order to store information
about the occurrence of various entities such as variable names, function names, objects, classes, interfaces,
etc. Symbol table is used by both the analysis and the synthesis parts of a compiler.
• A symbol table may serve the following purposes depending upon the language in hand:
• To store the names of all entities in a structured form at one place.
• To verify if a variable has been declared.
• To implement type checking, by verifying assignments and expressions in the source code are semantically
correct.To determine the scope of a name (scope resolution).
• A symbol table is simply a table which can be either linear or a hash table. It maintains an entry for each name
in the following format:
• <symbol name, type, attribute>
• For example, if a symbol table has to store information about the following variable declaration: • static int
interest;
• then it should store the entry such as:
• <interest, int, static>
• The attribute clause contains the entries related to the name.
Implementation
If a compiler is to handle a small amount of data, then the symbol table can be implemented as an unordered list,
which is easy to code, but it is only suitable for small tables only. A symbol table can be implemented in one of the
following ways:
•Linear (sorted or unsorted) list
•Binary Search Tree
•Hash table
Among all, symbol tables are mostly implemented as hash tables, where the source code symbol itself is treated as
a key for the hash function and the return value is the information about the symbol.
Operations
A symbol table, either linear or hash, should provide the following
operations. insert()
This operation is more frequently used by analysis phase, i.e., the first half of the compiler where tokens are
identified and names are stored in the table. This operation is used to add information in the symbol table about
unique names occurring in the source code. The format or structure in which the names are stored depends upon
the compiler in hand.
An attribute for a symbol in the source code is the information associated with that symbol. This information contains
the value, state, scope, and type about the symbol. The insert() function takes the symbol and its attributes as
arguments and stores the information in the symbol table. For example:
int a; should be processed by
the compiler as: insert(a, int);

lookup() lookup() operation is used to search a name in


the symbol table to determine:
•if the symbol exists in the table.
•if it is declared before it is being used.
•if the name is used in the scope.
•if the symbol is initialized.
•if the symbol declared multiple times.
The format of lookup() function varies according to the programming language. The basic format should match the
following:

lookup(symbol)
This method returns 0 (zero) if the symbol does not exist in the symbol table. If the symbol exists in the symbol
table, it returns its attributes stored in the table.
Scope Management
A compiler maintains two types of symbol tables: a global symbol table which can be accessed by all the procedures
and scope symbol tables that are created for each scope in the program.
To determine the scope of a name, symbol tables are arranged in hierarchical structure as shown in the example
below:

int value=10;

void pro_one()
{
int one_1;
int one_2;

{ \
int one_3; |_ inner scope 1
int one_4; |
} /

int one_5;

{ \
int one_6; |_ inner scope 2
int one_7; |
} /
void pro_two()
{
int two_1;
int two_2;

{ \
int two_3; |_ inner scope 3
int two_4; |
} /
int two_5;
}

The above program can be represented in a hierarchical structure of symbol tables:


The global symbol table contains names for one global variable (int value) and two procedure names, which should
be available to all the child nodes shown above. The names mentioned in the pro_one symbol table (and all its child
tables) are not available for pro_two symbols and its child tables. This symbol table data structure hierarchy is stored
in the semantic analyzer and whenever a name needs to be searched in a symbol table, it is searched using the
following algorithm:
•first a symbol will be searched in the current scope, i.e. current symbol table.
•if a name is found, then search is completed, else it will be searched in the parent symbol table until,
•either the name is found or global symbol table has been searched for the name.
Run-time environment
• A program as a source code is merely a collection of text (code, statements etc.) and to make it alive, it
requires actions to be performed on the target machine. A program needs memory resources to execute
instructions. A program contains names for procedures, identifiers etc., that require mapping with the
actual memory location at runtime.
• By runtime, we mean a program in execution. Runtime environment is a state of the target machine,
which may include software libraries, environment variables, etc., to provide services to the processes
running in the system.
• Runtime support system is a package, mostly generated with the executable program itself and facilitates
the process communication between the process and the runtime environment. It takes care of memory
allocation and de-allocation while the program is being executed.
• Activation Trees
• A program is a sequence of instructions combined into a number of procedures. Instructions in a procedure
are executed sequentially. A procedure has a start and an end delimiter and everything inside it is called the
body of the procedure. The procedure identifier and the sequence of finite instructions inside it make up the
body of the procedure.
• The execution of a procedure is called its activation. An activation record contains all the necessary
information required to call a procedure.
An activation record may contain the following units (depending upon the source language used).
Temporaries Stores temporary and intermediate values of an expression.

Local Data Stores local data of the called procedure.

Machine Status Stores machine status such as Registers, Program Counter etc., before the procedure is
called.

Control Link Stores the address of activation record of the caller procedure.

Access Link Stores the information of data which is outside the local scope.

Actual Parameters Stores actual parameters, i.e., parameters which are used to send input to the called
procedure.

Return Value Stores return values.


Whenever a procedure is executed, its activation record is stored on the stack, also known as control stack. When
a procedure calls another procedure, the execution of the caller is suspended until the called procedure finishes
execution. At this time, the activation record of the called procedure is stored on the stack.
We assume that the program control flows in a sequential manner and when a procedure is called, its control is
transferred to the called procedure. When a called procedure is executed, it returns the control back to the caller.
This type of control flow makes it easier to represent a series of activations in the form of a tree, known as the
activation tree.
To understand this concept, we take a piece of code as an example:
...
printf(“Enter Your Name: “);
scanf(“%s”, username);
show_data(username); printf(“Press
any key to continue…”);
...
int show_data(char *user)
{
printf(“Your name is %s”, username);
return 0;
}
...
Below is the activation tree of the code given.
Now we understand that procedures are executed in depth-first manner, thus stack allocation is the best suitable
form of storage for procedure activations.
Storage Allocation
Runtime environment manages runtime memory requirements for the following entities:
•Code : It is known as the text part of a program that does not change at runtime. Its memory requirements are
known at the compile time.
•Procedures : Their text part is static but they are called in a random manner. That is why, stack storage is used to
manage procedure calls and activations.
•Variables : Variables are known at the runtime only, unless they are global or constant. Heap memory allocation
scheme is used for managing allocation and de-allocation of memory for variables in runtime. Static Allocation
In this allocation scheme, the compilation data is bound to a fixed location in the memory and it does not change
when the program executes. As the memory requirement and storage locations are known in advance, runtime
support package for memory allocation and de-allocation is not required.
Stack Allocation
Procedure calls and their activations are managed by means of stack memory allocation. It works in last-in-first-out
(LIFO) method and this allocation strategy is very useful for recursive procedure calls.
Heap Allocation
Variables local to a procedure are allocated and de-allocated only at runtime. Heap allocation is used to dynamically
allocate memory to the variables and claim it back when the variables are no more required.
Except statically allocated memory area, both stack and heap memory can grow and shrink dynamically and
unexpectedly. Therefore, they cannot be provided with a fixed amount of memory in the system.
As shown in the image above, the text part of the code is allocated a fixed amount of memory. Stack and heap
memory are arranged at the extremes of total memory allocated to the program. Both shrink and grow against each
other.
Parameter Passing
The communication medium among procedures is known as parameter passing. The values of the variables from a
calling procedure are transferred to the called procedure by some mechanism. Before moving ahead, first go through
some basic terminologies pertaining to the values in a program.
r-value
The value of an expression is called its r-value. The value contained in a single variable also becomes an r-value if it
appears on the right-hand side of the assignment operator. r-values can always be assigned to some other variable.
l-value
The location of memory (address) where an expression is stored is known as the l-value of that expression. It always
appears at the left hand side of an assignment operator.
For example:
day = 1; week
= day * 7;
month = 1;
year = month
* 12;
From this example, we understand that constant values like 1, 7, 12, and variables like day, week, month and year,
all have r-values. Only variables have lvalues as they also represent the memory location assigned to them.
For example: 7 = x + y; is an l-value error, as the constant
7 does not represent any memory location.
Formal Parameters
Variables that take the information passed by the caller procedure are called formal parameters. These variables
are declared in the definition of the called function.
Actual Parameters
Variables whose values or addresses are being passed to the called procedure are called actual parameters. These
variables are specified in the function call as arguments. Example:
fun_one()
{
int actual_parameter = 10;
call fun_two(int actual_parameter);
}
fun_two(int formal_parameter)
{
print formal_parameter;
}
Formal parameters hold the information of the actual parameter, depending upon the parameter passing technique
used. It may be a value or an address.
Pass by Value
In pass by value mechanism, the calling procedure passes the r-value of actual parameters and the compiler puts
that into the called procedure’s activation record. Formal parameters then hold the values passed by the calling
procedure. If the values held by the formal parameters are changed, it should have no impact on the actual
parameters.
Pass by Reference
In pass by reference mechanism, the l-value of the actual parameter is copied to the activation record of the called
procedure. This way, the called procedure now has the address (memory location) of the actual parameter and the
formal parameter refers to the same memory location. Therefore, if the value pointed by the formal parameter is
changed, the impact should be seen on the actual parameter as they should also point to the same value.
Pass by Copy-restore
This parameter passing mechanism works similar to ‘pass-by-reference’ except that the changes to actual parameters
are made when the called procedure ends. Upon function call, the values of actual parameters are copied in the
activation record of the called procedure. Formal parameters if manipulated have no real-time effect on actual
parameters (as l-values are passed), but when the called procedure ends, the l-values of formal parameters are
copied to the l-values of actual parameters.
Example:
int y;
calling_procedure()
{ y = 10;
copy_restore(y); //l-value of y is passed
printf y; //prints 99
}
copy_restore(int x)
{
x = 99; // y still has value 10 (unaffected)
y = 0; // y is now 0
}
When this function ends, the l-value of formal parameter x is copied to the actual parameter y. Even if the value of
y is changed before the procedure ends, the l-value of x is copied to the l-value of y making it behave like call by
reference.
Pass by Name
Languages like Algol provide a new kind of parameter passing mechanism that works like preprocessor in C language.
In pass by name mechanism, the name of the procedure being called is replaced by its actual body. Pass-by-name
textually substitutes the argument expressions in a procedure call for the corresponding parameters in the body of
the procedure so that it can now work on actual parameters, much like pass-by-reference.

Procedure activation:
• Control stack is a run time stack which is used to keep track of the live
procedure activations i.e. it is used to find out the procedures whose
execution have not been completed.
• When it is called (activation begins) then the procedure name will push
on to the stack and when it returns (activation ends) then it will popped.
• Activation record is used to manage the information needed by a single
execution of a procedure.
• An activation record is pushed into the stack when a procedure is called
and it is popped when the control returns to the caller function.
• The diagram below shows the contents of activation records:
Return Value: It is used by calling procedure to return a value to calling procedure.

Actual Parameter: It is used by calling procedures to supply parameters to the called procedures.

Control Link: It points to activation record of the caller.

Access Link: It is used to refer to non-local data held in other activation records.

Saved Machine Status: It holds the information about status of machine before the procedure is called.

Local Data: It holds the data that is local to the execution of the procedure.

Temporaries: It stores the value that arises in the evaluation of an expression.

parameter passing:
• There are different ways in which parameter data can be passed into and out of methods and functions. Let us
assume that a function B() is called from another function A(). In this case A is called the “caller function” and
B is called the “called function or callee function”. Also, the arguments which A sends to B are called actual
arguments and the parameters of B are called formal arguments.
• Terminology
• Formal Parameter : A variable and its type as they appear in the prototype of the function or method.
• Actual Parameter : The variable or expression corresponding to a formal parameter that appears in the
function or method call in the calling environment.

• Modes:
• IN: Passes info from caller to callee.
• OUT: Callee writes values in caller.
• IN/OUT: Caller tells callee value of variable, which may be updated by callee.
• Important methods of Parameter Passing
• Pass By Value: This method uses in-mode semantics. Changes made to formal parameter do not get
transmitted back to the caller. Any modifications to the formal parameter variable inside the called
function or method affect only the separate storage location and will not be reflected in the actual
parameter in the calling environment. This method is also called as call by value.
// C program to illustrate
// call by value
#include <stdio.h>

void func(int a, int b)


{ a += b;
printf("In func, a = %d b = %d\n", a, b);
}
int main(void)
{ int x = 5, y = 7;

// Passing parameters
func(x, y);
printf("In main, x = %d y = %d\n", x, y);
return 0;
}

Output:
In func, a = 12 b = 7
In main, x = 5 y = 7
1.Languages like C, C++, Java support this type of parameter passing. Java in fact is strictly call by value.
Shortcomings:
1. Inefficiency in storage allocation
2. For objects and arrays, the copy semantics are costly
2.Pass by reference(aliasing): This technique uses in/out-mode semantics. Changes made to formal
parameter do get transmitted back to the caller through parameter passing. Any changes to the formal
parameter are reflected in the actual parameter in the calling environment as formal parameter receives a
reference (or pointer) to the actual data. This method is also called as call by reference. This method is
efficient in both time and space.
// C program to illustrate
// call by reference
#include <stdio.h>

void swapnum(int* i, int* j)


{ int temp = *i; *i = *j;
*j = temp;
}

int main(void)
{ int a = 10, b = 20;

// passing parameters
swapnum(&a, &b);

printf("a is %d and b is %d\n", a, b);


return 0;
}
Output:

a is 20 and b is 10
Other methods of Parameter Passing
These techniques are older and were used in earlier programming languages like Pascal, Algol and Fortran. These
techniques are not applicable in high level languages.
1.Pass by Result:This method uses out-mode semantics. Just before control is transferred back to the caller, the
value of the formal parameter is transmitted back to the actual parameter.T his method is sometimes called call by
result. In general, pass by result technique is implemented by copy.
2.Pass by Value-Result: This method uses in/out-mode semantics. It is a combination of Pass-by-Value and Pass-
byresult. Just before the control is transferred back to the caller, the value of the formal parameter is transmitted back
to the actual parameter. This method is sometimes called as call by value-result
3.Pass by name : This technique is used in programming language such as Algol. In this technique, symbolic “name”
of a variable is passed, which allows it both to be accessed and update.
Example:
To double the value of C[j], you can pass its name (not its value) into the following procedure.
procedure
double(x); real x;
begin x:=x*2
end;
In general, the effect of pass-by-name is to textually substitute the argument in a procedure call for the corresponding
parameter in the body of the procedure.
Implications of Pass-by-Name mechanism:
•The argument expression is re-evaluated each time the formal parameter is passed.
•The procedure can change the values of variables used in the argument expression and hence change the
expression’s value.

Value Return:
• The value of expression, if present, is returned to the calling function. If expression is omitted, the return
value of the function is undefined. The expression, if present, is evaluated and then converted to the
type returned by the function. When a return statement contains an expression in functions that have a
void return type, the compiler generates a warning, and the expression isn't evaluated.
• If no return statement appears in a function definition, control automatically returns to the calling
function after the last statement of the called function is executed. In this case, the return value of the
called function is undefined. If the function has a return type other than void, it's a serious bug, and the
compiler prints a warning diagnostic message. If the function has a void return type, this behavior is
okay, but may be considered poor style. Use a plain return statement to make your intent clear.
• As a good engineering practice, always specify a return type for your functions. If a return value isn't
required, declare the function to have void return type. If a return type isn't specified, the C compiler
assumes a default return type of int.
• Many programmers use parentheses to enclose the expression argument of the return statement.
However, C doesn't require the parentheses.
• The compiler may issue a warning diagnostic message about unreachable code if it finds any
statements placed after the return statement.
• In a main function, the return statement and expression are optional. What happens to the returned
value, if one is specified, depends on the implementation. Microsoft-specific: The Microsoft C
implementation returns the expression value to the process that invoked the program, such as cmd.exe.
If no return expression is supplied, the Microsoft C runtime returns a value that indicates success (0) or
failure (a non-zero value).
Example
• This example is one program in several parts. It demonstrates the return statement, and how it's used
both to end function execution, and optionally, to return a value. // C_return_statement.c
// Compile using: cl /W4 C_return_statement.c
#include <limits.h> // for INT_MAX
#include <stdio.h> // for printf
long long square( int value )
{
// Cast one operand to long long to force the //
expression to be evaluated as type long long. // Note
that parentheses around the return expression // are
allowed, but not required here.
return ( value * (long long) value );
}
The square function returns the square of its argument, in a wider type to prevent an arithmetic
error. Microsoft-specific: In the Microsoft C implementation, the long long type is large enough to
hold the product of two int values without overflow.
The parentheses around the return expression in square are evaluated as part of the expression, and
aren't required by the return statement.
memory allocation:
• Since C is a structured language, it has some fixed rules for programming. One of them includes
changing the size of an array. An array is a collection of items stored at contiguous memory locations.

• As it can be seen that the length (size) of the array above made is 9. But what if there is a
requirement to change this length (size). For Example,
• If there is a situation where only 5 elements are needed to be entered in this array. In this case, the
remaining 4 indices are just wasting memory in this array. So there is a requirement to lessen the
length (size) of the array from 9 to 5.
•Take another situation. In this, there is an array of 9 elements with all 9 indices filled. But there is a need
to enter 3 more elements in this array. In this case, 3 indices more are required. So the length (size) of the
array needs to be changed from 9 to 12.
This procedure is referred to as Dynamic Memory Allocation in C.
Therefore, C Dynamic Memory Allocation can be defined as a procedure in which the size of a data
structure (like Array) is changed during the runtime.
C provides some functions to achieve these tasks. There are 4 library functions provided by C defined
under <stdlib.h> header file to facilitate dynamic memory allocation in C programming. They are:
1.malloc()
2.calloc()
3.free()
4.realloc()
C malloc() method
The “malloc” or “memory allocation” method in C is used to dynamically allocate a single large block of
memory with the specified size. It returns a pointer of type void which can be cast into a pointer of any
form. It doesn’t Initialize memory at execution time so that it has initialized each block with the default

Synatxgarbage value initially: . ptr = (cast-type*)


malloc(byte-size) For Example:
ptr = (int*) malloc(100 * sizeof(int));
Since the size of int is 4 bytes, this statement will allocate 400 bytes of memory. And, the pointer ptr holds
the address of the first byte in the allocated memory.

If space is insufficient, allocation fails and returns a NULL pointer.


Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
// This pointer will hold the // base
address of the block created
int* ptr;
int n, i;

// Get the number of elements for the array


printf("Enter number of elements:");
scanf("%d",&n); printf("Entered number of
elements: %d\n", n);

// Dynamically allocate memory using malloc()


ptr = (int*)malloc(n * sizeof(int));

// Check if the memory has been successfully


// allocated by malloc or not if (ptr == NULL)
{ printf("Memory not allocated.\n");
// Memory has been successfully allocated
printf("Memory successfully allocated using
malloc.\n");

// Get the elements of the array


for (i = 0; i < n; ++i) { ptr[i]
= i + 1;
}
// Print the elements of the array
printf("The elements of the array are: ");
for (i = 0; i < n; ++i) {
printf("%d, ", ptr[i]);
}
}

return 0;
}

Output:
Enter number of elements: 5
Memory successfully allocated using malloc.
The elements of the array are: 1, 2, 3, 4, 5,
C calloc() method
1.“calloc” or “contiguous allocation” method in C is used to dynamically allocate the specified number of blocks of
memory of the specified type. it is very much similar to malloc() but has two different points and these are:
2.It initializes each block with a default value ‘0’.
3.It has two parameters or arguments as compare to malloc().
Syntax:
ptr = (cast-type*)calloc(n, element-size); here, n is the no. of elements and
element-size is the size of each element. For Example:

ptr = (float*) calloc(25, sizeof(float));


This statement allocates contiguous space in memory for 25 elements each with the size of the float

If space is insufficient, allocation fails and returns a NULL pointer.


Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{

// This pointer will hold the // base


address of the block created int*
ptr; int n, i;
// Get the number of elements for the array
n = 5; printf("Enter number of elements:
%d\n", n);

// Dynamically allocate memory using calloc()


ptr = (int*)calloc(n, sizeof(int));

// Check if the memory has been successfully


// allocated by calloc or not
if (ptr == NULL) { printf("Memory not allocated.\n");
exit(0);
} else
{

// Memory has been successfully allocated


printf("Memory successfully allocated using calloc.\n");

// Get the elements of the array


for (i = 0; i < n; ++i) {
ptr[i] = i + 1;
}

// Print the elements of the array


printf("The elements of the array are: ");
for (i = 0; i < n; ++i) {
printf("%d, ", ptr[i]);
}
}

return 0;
}
Output:
Enter number of elements: 5
Memory successfully allocated using calloc.
The elements of the array are: 1, 2, 3, 4, 5,
C free() method
“free” method in C is used to dynamically de-allocate the memory. The memory allocated using functions malloc() and
calloc() is not de-allocated on their own. Hence the free() method is used, whenever the dynamic memory allocation
takes place. It helps to reduce wastage of memory by freeing it.
Syntax:
free(ptr);
#include <stdio.h>
#include <stdlib.h>

int main()
{

// This pointer will hold the // base


address of the block created
int *ptr, *ptr1;
int n, i;
// Get the number of elements for the array
n = 5; printf("Enter number of elements:
%d\n", n);

// Dynamically allocate memory using malloc()


ptr = (int*)malloc(n * sizeof(int));

// Dynamically allocate memory using calloc()


ptr1 = (int*)calloc(n, sizeof(int));

// Check if the memory has been successfully


// allocated by malloc or not
if (ptr == NULL || ptr1 == NULL) {
printf("Memory not allocated.\n");
exit(0);
} else
{

// Memory has been successfully allocated


printf("Memory successfully allocated using malloc.\n");

// Free the memory


free(ptr);
printf("Malloc Memory successfully freed.\n");

// Memory has been successfully allocated


printf("\nMemory successfully allocated using calloc.\n");

// Free the memory


free(ptr1);
printf("Calloc Memory successfully freed.\n");
}

return 0;
}
Output:
Enter number of elements: 5
Memory successfully allocated using malloc.
Malloc Memory successfully freed.
Memory successfully allocated using calloc.
Calloc Memory successfully freed.
C realloc() method
“realloc” or “re-allocation” method in C is used to dynamically change the memory allocation of a
previously allocated memory. In other words, if the memory previously allocated with the help of malloc or
calloc is insufficient, realloc can be used to dynamically re-allocate memory. re-allocation of memory
maintains the already present value and new blocks will be initialized with the default garbage value.
Syntax:
ptr = realloc(ptr, newSize); where ptr is
reallocated with new size 'newSize'.

If space is insufficient, allocation fails and returns a NULL pointer.


Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{

// This pointer will hold the // base


address of the block created
int* ptr;
int n, i;

// Get the number of elements for the array


n = 5; printf("Enter number of elements:
%d\n", n);

// Dynamically allocate memory using calloc()


ptr = (int*)calloc(n, sizeof(int));

// Check if the memory has been successfully


// allocated by malloc or not if (ptr == NULL)
{ printf("Memory not allocated.\n");
exit(0);}
else {
// Memory has been successfully allocated
printf("Memory successfully allocated using calloc.\n");

// Get the elements of the array


for (i = 0; i < n; ++i) {
ptr[i] = i + 1;
}

// Print the elements of the array


printf("The elements of the array are: ");
for (i = 0; i < n; ++i) {
printf("%d, ", ptr[i]);
}

// Get the new size for the array


n = 10;
printf("\n\nEnter the new size of the array: %d\n", n);

// Dynamically re-allocate memory using realloc()


ptr = realloc(ptr, n * sizeof(int));
// Memory has been successfully allocated printf("Memory successfully re-
allocated using realloc.\n");
// Get the new elements of the array
for (i = 5; i < n; ++i) {
ptr[i] = i + 1;
}
// Print the elements of the array
printf("The elements of the array are: ");
for (i = 0; i < n; ++i) {
printf("%d, ", ptr[i]);
}

free(ptr);
} return

0;}

Output:
Enter number of elements:
5 Memory successfully allocated using calloc.
The elements of the array are: 1, 2, 3, 4, 5,
Enter the new size of the array:
10 Memory successfully re-allocated using realloc. The elements of the array are: 1,
2, 3, 4, 5, 6, 7, 8, 9, 10,
Scope:
• The scope of a variable x in the region of the program in which the use of x refers to its
declaration. One of the basic reasons for scoping is to keep variables in different parts of the
program distinct from one another. Since there are only a small number of short variable
names, and programmers share habits about naming of variables (e.g., I for an array index), in
any program of moderate size the same variable name will be used in multiple different
scopes.
Scoping is generally divided into two classes:
1. Static Scoping
2. Dynamic Scoping Static Scoping:
Static scoping is also called lexical scoping. In this scoping, a variable always refers to its
top-level environment. This is a property of the program text and is unrelated to the run-time
call stack. Static scoping also makes it much easier to make a modular code as a programmer
can figure out the scope just by looking at the code. In contrast, dynamic scope requires the
programmer to anticipate all possible dynamic contexts.
In most programming languages including C, C++, and Java, variables are always statically
(or lexically) scoped i.e., binding of a variable can be determined by program text and is
independent of the run-time function call stack.
For example, the output for the below program is 10, i.e., the value returned by f() is not
dependent on who is calling it (Like g() calls it and has a x with value 20). f() always returns
the value of global variable x.
// A C program to demonstrate static scoping.
#include<stdio.h>
int x = 10;

// Called by g()
int f()
{ return
x; }

// g() has its own variable


// named as x and calls f()
int g() {
int x = 20;
return f();
}

int main()
{ printf("%d",
g()); printf("\n");
return 0;
}
Output :
10
To sum up, in static scoping the compiler first searches in the current block, then in global
variables, then in successively smaller scopes.
Dynamic Scoping:
With dynamic scope, a global identifier refers to the identifier associated with the most recent
environment and is uncommon in modern languages. In technical terms, this means that each
identifier has a global stack of bindings and the occurrence of an identifier is searched in the
most recent binding.
In simpler terms, in dynamic scoping, the compiler first searches the current block and then
successively all the calling functions.
// Since dynamic scoping is very uncommon in
// the familiar languages, we consider the
// following pseudo code as our example. It
// prints 20 in a language that uses dynamic
// scoping. int x = 10;

// Called by g()
int f()
{ return
x;
}
// g() has its own variable
// named as x and calls f()
int g() {
int x =
20;
return
f();
}
main()
{
printf(g());
}
Output in a language that uses Dynamic Scoping :
20

Static Vs Dynamic Scoping


In most programming languages static scoping is dominant. This is simply because in static scoping it’s easy to
reason about and understand just by looking at code. We can see what variables are in the scope just by looking at
the text in the editor.
Dynamic scoping does not care about how the code is written, but instead how it executes. Each time a new
function is executed, a new scope is pushed onto the stack.
Perl supports both dynamic and static scoping. Perl’s keyword “my” defines a statically scoped local variable, while
the keyword “local” defines a dynamically scoped local variable.
# A perl code to demonstrate dynamic scoping
$x = 10;
sub f
{
return $x;
}
sub g
{
# Since local is used, x uses
# dynamic scoping. local
$x = 20;

return f(); }
print g()."\n";

Output :
20

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy