2. Question Bank With Answers of Compiler Design (CS-603(C ) ).Doc-1
2. Question Bank With Answers of Compiler Design (CS-603(C ) ).Doc-1
Question Bank
Unit 3
Ans= Type checking is the process of verifying and enforcing constraints of types in values. A
compiler must check that the source program should follow the syntactic and semantic
conventions of the source language and it should also check the type rules of the language.
It checks the type of objects and reports a type error in the case of a violation, and incorrect
types are corrected. Whatever the compiler we use, while it is compiling the program, it has
to follow the type rules of the language. Every language has its own set of type rules for the
language. We know that the information about data types is maintained and computed by the
compiler.
Static type checking is defined as type checking performed at compile time. It checks the type
variables at compile-time, which means the type of the variable is known at the compile time.
It generally examines the program text during the translation of the program. Using the type
rules of a system, a compiler can infer from the source text that a function (fun) will be
applied to an operand (a) of the right type each time the expression fun(a) is evaluated.
Dynamic Type Checking is defined as the type checking being done at run time. In Dynamic
Type Checking, types are associated with values, not variables. Implementations of
dynamically type-checked languages runtime objects are generally associated with each other
through a type tag, which is a reference to a type containing its type information. Dynamic
typing results in more compact programs since it is more flexible and does not require types
to be spelled out.
Ans= The type expressions are used to represent the type of a programming language
construct. Type expression can be a basic type or formed by recursively applying an operator
called a type constructor to other type expressions. The basic types and constructors depend
on the source language to be verified. Let us define type expression as follows:
A record differs from a product. The fields of a record have names. The record type
constructor will be applied to a tuple formed from field types and field names. For example,
the Pascal program fragment
address: integer ;
end;
● Pointer: Pointer(T) is a type expression denoting the type “pointer to an object of type
T where T is a type expression. For example, in Pascal, the declaration var ptr: *row
declares variable “ptr” to have type pointer(row).
Ans= The programming languages that enable mixed-mode expressions should describe
conventions for implicit operand type conversions. Coercion is defined as an automatic
conversion between types. For example in Pascal, if the operands for the addition operation
are of integer type and other real types, one of then the integer data object is implicitly
changed to type real earlier the addition is implemented.
For example, Pascal supports a built-in function round that changes a real-number data object
to an integer data object with a value similar to the rounded value of the real.
Ans= A piece of code is said to be polymorphic if the statements in the body can be executed
with different types. A function that takes the arguments of different types and executes the
same code is a polymorphic function. The type checker designed for a language like Ada that
supports polymorphic functions, the type expressions are extended to include the expressions
that vary with type variables. The same operation performed on different types is called
overloading and are often found in object-oriented programming. For example, let us
consider the function that takes two arguments and returns the result.
#include <bits/stdc++.h>
// Formal parameter
int temp = x;
x = y;
y = temp;
int main()
// Actual parameter
int x = 9;
int y = 12;
cout << "Values before swap: x= " << x << " y= " << y << endl;
swap(x, y);
cout << "Values after swap: x= " << x << " y= " << y << endl;
As we can see, even when we modify the contents of variables x and y defined in the scope of
the swap function, the modifications do not affect the variables' definitions in the scope of the
main. This is because main() will not be affected by changes performed in the swap() because
we call swap() by value, which will receive separate memory for x and y.
Call by Reference: The formal and actual parameters in a call by reference relate to the same
memory address. The activation record of the called function receives a copy of the L-value
of the actual arguments. As a result, the address of the actual parameters is passed to the
called function.
If the actual parameters do not contain an L-value, they are evaluated in a new temporary
location, and the location's address is passed. Since changes are made at the address, any
modifications to the formal parameter are reflected in the actual parameters. Let us see an
example of a call by reference.
#include <stdio.h>
// Call by reference
// Formal parameter
*x = *y;
*y = temp;
int main() {
// Actual parameter
int x = 9;
int y = 12;
swap(&x, &y);
As we can see, instead of int x, int y, we used int *x, int y, and instead of giving x,y, we gave
&x,&y in the function call. Due to the usage of pointers as function parameters, which return
the original parameters' address rather than their value, this practice is called by reference.
The variables' addresses are given using the & operator, and the memory location to which
the pointer is pointing is accessed using the * operator. The modifications done in
the swap() reflect in main(), as shown in the output above because the variable function
points to the same memory address as the original arguments.
Q.7 Explain Call by copy restore and Call by name technique of parameter passing with
examples?
Ans= Call by Copy Restore: In contrast to call-by-reference, changes to actual parameters
are made when the called procedure completes. The actual parameter values are stored in the
called procedure's activation record during the function call.
The manipulation of formal parameters has no immediate impact on the actual parameters;
however, the L-value of the formal parameters is copied to the actual parameters when the
called procedure completes.
#include <bits/stdc++.h>
int a;
void func(int x) {
//a is still 10
x = 5;
//a is now 5
a = x;
int main()
a = 10;
func(a);
Value is passed into the function in the example above, but until the function is complete, it
has no impact on the value of the passed-in variable. At that time, the function variable's final
value is saved in the passed-in variable.
Call by Name: A new form of preprocessor-like argument parsing mechanism is offered by
languages like Algol. Here, the procedure's body is called instead of the procedure's name.
#include <bits/stdc++.h>
int a, b, c;
// Formal parameter
int t;
t = a;
a = b;
b = t;
int main() {
// Actual parameter
int a = 9;
int b = 12;
swap(a, b);
Here, the evaluation is done based on parameters. In all cases where formal parameters are
used in the technique, actual parameters are used instead of formal ones.
Q.8 What is Symbol Table ? Explain its features .
Ans= A symbol table is an important Data Structure used by compilers to manage identifiers
in a program. An identifier is a name given to a variable, function, class or other
programming construct that is used to represent some data or functionality in a program.
Identifiers must be declared before they are used in a program, and their properties (such as
data type, scope, and memory location) must be known by the compiler to generate the
correct code. A symbol table provides a way for the compiler to store this information and
access it as needed during the compilation process.
A symbol table typically consists of a set of entries, each of which represents an identifier
used in the program. Each entry contains information such as the identifier's name, data type,
scope, memory location, and any other attributes that may be needed by the compiler. The
symbol table is created and populated during the compilation phase, as the compiler scans the
program for identifiers and their declarations. Once the symbol table is populated, it can be
used by the compiler in several ways. For example, the compiler can use the symbol table to
check for errors in the program, such as undeclared variables or type mismatches.
Lexical Analysis: New table entries are created in the table, For example, entries about
tokens.
Syntax Analysis: Adds the information about attribute type,, dimension, scope line of
reference, use, etc in the table.
Semantic Analysis: Checks for semantics in the table, i.e., verifies that expressions and
assignments are semantically accurate (type checking) and updates the table appropriately.
Intermediate Code generation: The symbol table is used to determine how much and what
type of run-time is allocated, as well as to add temporary variable data.
Code Optimization: Uses information from the symbol table for machine-dependent
optimization.
Target Code generation: Uses the address information of the identifier in the table to
generate code.
Q.10 How Symbol Table is implemented ? Explain .
Ans= If a compiler only needs to process a small quantity of data, the symbol table can be
implemented as an unordered list, which is simple to code but only works for small tables.
The following methods can be used to create a symbol table:
1. List: A List is a collection of elements in which each element has a position or an
index. This is one of the way to create a symbol table is to use a List to store the
entries for each identifier. This method is simple and easy to implement, but it may
not be efficient for large symbol tables because searching for a specific identifier
requires iterating through the entire List.
2. Linked List: A Linked List is a data structure in which each element contains a
reference to the next element in the list. One way to create a symbol table is to use a
Linked List to store the entries for each identifier. This method can be more efficient
than a List for searching because it allows for faster traversal of the entries. However,
it can be slower than other methods for inserting or deleting entries because it requires
modifying the references between elements.
3. Binary Search Tree: A Binary Search Tree is a data structure in which each node has
at most two children, and the values in the left subtree are less than the value in the
node, and the values in the right subtree are greater than the value in the node. One
way to create a symbol table is to use a Binary Search Tree to store the entries for
each identifier. This method is efficient for searching because it allows for a binary
search to quickly find a specific identifier. However, it can be slower than other
methods for inserting or deleting entries because it may require rebalancing the tree.
4. Hash Table: A Hash Table is a data structure that uses a hash function to map keys to
values. One way to create a symbol table is to use a Hash Table to store the entries for
each identifier. This method is efficient for searching, inserting, and deleting entries
because it allows for constant-time access to entries. However, it may require more
memory than other methods because it needs to store the hash table and handle
collisions between entries.
The flow of control checks: Statements that cause the flow of control to leave a construct
must have someplace to which to transfer the flow of control. For example, a break statement
in C causes control to leave the smallest enclosing while, for, or switch statement, an error
occurs if such an enclosing statement does not exist.
Uniqueness checks: There are situations in which an object must be defined only once. For
example, in Pascal an identifier must be declared uniquely, labels in a case statement must be
distinct, and else a statement in a scalar type may not be represented.
Name-related checks: Sometimes the same name may appear two or more times. For
example in Ada, a loop may have a name that appears at the beginning and end of the
construct. The compiler must check that the same name is used at both places.
Ans= There are the following advantages of type conversion which are as follows −
● If during type checking, a mismatch appears between the actual type of an argument
and the expected type for that operation, then type conversion easily converts the data
object implicitly and prevents the error.
● There is no data is hidden. Because each short integer can be defined as a long
integer, no data is hidden by necessarily invoking a short int→long int.
● With dynamic type checking, conversions or coercions are built at the point that the
type mismatch is recognized during execution. For such languages narrowing
conversions can be enabled. For static type checking, more code is added to the
compiled program.
● Type conversion is a subtitle need when there is a large number of data types in a
programming language.
Like operator overloading, the function can also be overloaded. In function overloading, the
functions have the same name but different numbers and arguments of different types. In
Ada, the operator “*” has the standard meaning that it takes a pair of integers and returns an
integer. The function of “*” can be overloaded by adding the following declarations:
Function “*”(a,b: integer) return integer. Function “*”(a,b: complex) return integer. Function
“*”(a,b: complex) return complex.
By addition of the above declarations, now the operator “*” can take the following possible
types:
E → E1(E2)
{
E.type : = t
E2.type : = t → u then
E.type : = u
else E.type : = type_error
E′ → E {E′.type := E.type}
E → id {E.type := lookup(id.entry)}
Statement Mode recovery :In this method, when a parser encounters an error, it performs
the necessary correction on the remaining input so that the rest of the input statement allows
the parser to parse ahead. The correction can be deletion of extra semicolons, replacing the
comma with semicolons, or inserting a missing semicolon.While performing correction,
utmost care should be taken for not going in an infinite loop. A disadvantage is that it finds it
difficult to handle situations where the actual error occurred before pointing of detection.
Advantages:
Improved code quality: Error detection and recovery in a compiler can improve the overall
quality of the code produced. This is because errors can be identified early in the compilation
process and addressed before they become bigger issues.
Increased productivity: Error recovery can also increase productivity by allowing the
compiler to continue processing the code after an error is detected. This means that
developers do not have to stop and fix every error manually, saving time and effort.
Better user experience: Error recovery can also improve the user experience of software
applications. When errors are handled gracefully, users are less likely to become frustrated
and are more likely to continue using the application.
Better debugging: Error recovery in a compiler can help developers to identify and debug
errors more efficiently. By providing detailed error messages, the compiler can assist
developers in pinpointing the source of the error, saving time and effort.
Consistent error handling: Error recovery ensures that all errors are handled in a consistent
manner, which can help to maintain the quality and reliability of the software being
developed.
Reduced maintenance costs: By detecting and addressing errors early in the development
process, error recovery can help to reduce maintenance costs associated with fixing errors in
later stages of the software development lifecycle.
Improved software performance: Error recovery can help to identify and address code that
may cause performance issues, such as memory leaks or inefficient algorithms. By improving
the performance of the code, the overall performance of the software can be improved as
well.
Disadvantages:
Slower compilation time: Error detection and recovery can slow down the compilation
process, especially if the recovery mechanism is complex. This can be an issue in large
software projects where the compilation time can be a bottleneck.
Increased complexity: Error recovery can also increase the complexity of the compiler,
making it harder to maintain and debug. This can lead to additional development costs and
longer development times.
Risk of silent errors: Error recovery can sometimes mask errors in the code, leading to silent
errors that go unnoticed. This can be particularly problematic if the error affects the behavior
of the software application in subtle ways.
Potential for incorrect recovery: If the error recovery mechanism is not implemented
correctly, it can potentially introduce new errors or cause the code to behave unexpectedly.
Dependency on the recovery mechanism: If developers rely too heavily on the error
recovery mechanism, they may become complacent and not thoroughly check their code for
errors. This can lead to errors being missed or not addressed properly.
Difficulty in diagnosing errors: Error recovery can make it more difficult to diagnose and
debug errors since the error message may not accurately reflect the root cause of the issue.
This can make it harder to fix errors and may lead to longer development times.
Compatibility issues: Error recovery mechanisms may not be compatible with certain
programming languages or platforms, leading to issues with portability and cross-platform
development.