What is an example of lack of orthogonality in C?
• Structures (but not arrays) maybe returned from a function.
• An array can be returned if it is inside a structure.
• Everything is passed by value (except arrays).
[1-2] Why is readability important to writability?
• Readability directly impacts writability because the easier a language is
to read and understand, the the easier it becomes to writes new code
efficienty with fewer mistakes and greater maintainability.
[1-3] Strings are supported by all modern programming languages,
but no consensus has emerged among language designers on how
strings should be classified. What consequences have it for the programmer to have strings as simple types or as structured types, respectively?
• Simple Type Strings: Easier to use, safer, and preferred in high-level
languages but can be inefficient for frequent modifications.
• Structured Type Strings: More powerful and efficient in terms of memory
management but require careful handling and are more error-prone.
[1-4] What is the disadvantage of having too many features in a language?
1. Reduced Readability.
2. Increased Learning Difficulty.
3. More Complexity in Writing Code.
4. Reduced Maintainability and Reliability.
[3-1] What would the tokens be in the following statement: if (a < 5)
then b = a?
1. if → Keyword
2. ( → Left Parenthesis (Symbol)
3. a → Identifier (Variable Name)
4. < → Relational Operator
5. 5 → Integer Literal
6. ) → Right Parenthesis (Symbol)
7. then → Keyword.
8. b → Identifier (Variable Name)
9. = → Assignment Operator
1
10. a → Identifier (Variable Name)
[3-2] Use the grammar shown below to draw the parse tree for “3 * 9 + 5 =”
< Calc > → < Expr > =
< Expr > → < Value > | < Value > < Oper > < Expr >
< Value > → < Digit > | < Sign >< Digit >
< Digit > → 3 | 5 | 9
< Oper > → + | - | * | /
< Sign > → - | +
• 3 * -9 + 5 =
• 3 is a
• * is an
• -9 is a (- as , 9 as )
•
– is an
• 5 is a
• = is part of
[3-3] Do a leftmost derivation of the string a = b * (c + a) given the
shown grammar: Write each step in the derivation on a separate line.
S -> ID = E
ID -> a | b | c
E -> E + E | E * E | ( E) | ID
• S
• → ID = E
• →a=E
• →a=E*E
• →a=b*E
• → a = b * (E)
• → a = b * (E + E)
• → a = b * (c + E)
• → a = b * (c + a)
2
[4-1] An identifier in a programming language can consist of only
letters (upper and lowercase) and digits, and must start with a capital
letter and end with a digit. Draw a finite automaton (state diagram)
for this and be sure to properly denote the accepting states.
(Start) --> [q0] --(A-Z)--> [q1] --(A-Z, a-z, 0-9)--> [q1]
|
v
(0-9)
|
v
[q2] (Accepting) --(0-9)--> [q2]
Current State
Input
Next State
q0
q1
q1
q2
A-Z
A-Z
0-9
0-9
q1
a-z
q2
q2
Perform the pairwise disjointness test for the following grammar
rules:
1. A → aB | b | cBB
• FIRST(aB) = { a }
• FIRST(b) = { b }
• FIRST(cBB) = { c }
• No intersection among {a}, {b}, and {c} → Passes the test.
2. B → aB | bA | aBb
• FIRST(aB) = { a }
• FIRST(bA) = { b }
• FIRST(aBb) = { a }
• Overlap exists between {aB} and {aBb} → Fails the test.
3. C → aaA | b | caB
• FIRST(aaA) = { aa }
• FIRST(b) = { b }
• FIRST(caB) = { c }
• No intersection among {aa}, {b}, and {c} → Passes the test.
3
[5-1] Consider the following C program:
For each of the 4 marked points in this function, list each visible variable, along with the number of the definition statement that defines
it.
void fun(void) {
int a, b, c; /* definition 1 */
.........
while (.........) {
int b, c, d; /* definition 2 */
......... //(1)
while (.........) {
int c, d, e; /* definition 3 */
......... //(2)
}
......... //(3)
}
......... //(4)
}
• a → from definition 1 (visible, function scope).
• b → from definition 2 (shadows b from definition 1).
• c → from definition 2 (shadows c from definition 1).
• d → from definition 2.
[5-2] Assume the following Ada program was compiled and executed
using static scoping rules. What value of x is printed in procedure Sub1? Under dynamic scoping rules, what value of x would
be printed? (show how you reached the result in both cases)
procedure Main is
x: Integer; -- (Global scope, declared in Main)
procedure Sub1 is
begin -- of Sub1
Put(x);
end; -- of Sub1
procedure Sub2 is
x: Integer; -- (Local to Sub2)
begin -- of Sub2
x := 10; -- This modifies the local x of Sub2
Sub1; -- Call Sub1
end; -- of Sub2
4
begin -- of Main
x := 5; -- Assign 5 to the global x
Sub2; -- Call Sub2
end; -- of Main
• Static Scoping: Sub1 refers to x defined in Main because that is the closest
enclosing scope at definition. The value of x in Main is still 5 (not affected
by Sub2). Output: 5.
• Since Sub1 is called inside Sub2, it uses x from Sub2, because Sub2 is the
most recent activation record. x in Sub2 is 10. Output: 10.
[5-3] Explain with examples what “coercion” is.
• Coercion is an implicit type conversion performed by a compiler or interpreter when an operation involves operands of different types.
console.log(5 + "10");
In this example:
• JavaScript converts the number 5 into a string.
• Concatenates it with “10”.
[6-1] Multidimensional arrays can be stored in row-major order or in
column-major order. Develop the access function of a column-major
order arrangement for 2- dimensional arrays.
int column_major_address(int base, int i, int j, int rows, int element_size) {
return base + (i + j * rows) * element_size;
}
[6-2] Describe the two main problems with using pointers in C, providing code examples as needed.
1. Dangling Pointers
• A dangling pointer arises when a pointer still points to a memory
location that has been freed or deallocated. Accessing such memory
can lead to undefined behavior, crashes, or corrupted data.
#include <stdio.h>
#include <stdlib.h>
int main() {
5
int *ptr = (int *)malloc(sizeof(int));
*ptr = 42; // Assign value
// Allocate memory
free(ptr); // Free memory
printf("%d\n", *ptr); // Undefined behavior: ptr is now dangling!
return 0;
}
2. Memory Leaks
• A memory leak occurs when memory is dynamically allocated but
never freed. Over time, the program consumes more memory, leading
to performance degradation and crashes.
#include <stdlib.h>
void leakMemory() {
int *ptr = (int *)malloc(10 * sizeof(int));
ptr[0] = 100; // Use the memory
// Memory is never freed!
}
// Allocate memory
int main() {
while (1) { // Infinite loop, continuously leaking memory
leakMemory();
}
return 0;
}
[6-3] Discuss implementation choices of character strings in various
programming languages. Mention at least three choices.
1. Null-Terminated Strings (C, C++).
2. Length-Prefixed Strings (Pascal, Swift, .NET Strings).
3. Immutable Unicode Strings (Python, Java, C#).
[7-1] Describe short-circuit evaluation of expressions in programming
languages, giving at least two examples (one should be a numeric
expression and another should be Boolean).
• Short-circuit evaluation is a technique where an expression is evaluated
only as far as necessary to determine its final result. If the outcome is already known before evaluating all operands, the remaining expressions are
skipped, improving efficiency and preventing unnecessary computations or
errors.
6
1. Boolean Short-Circuit Evaluation.
if (x != 0 && y / x > 2) {
printf("Valid expression");
}
2. Numeric Short-Circuit Evaluation.
int result = (x != 0) ? (y / x) : 0;
[7-2] Consider the following C program:
int fun(int *i) {
*i += 5;
return 4;
}
void main() {
int x = 3;
x = x + fun(&x);
}
What is the value of x after the assignment statement in main, assuming a)
operands are evaluated left-to-right. b) operands are evaluated right-to-left.
1. left-to-right x = 7
2. right-to-left x = 12
[8-1] In the Java example:
if (sum == 0)
if (count == 0)
result = 0;
else result = 1;
Which if gets the else? How could you force the else to go with the
other if?
• first if gets the else
• add curly brackets
if (sum == 0){
if (count == 0){
result = 0;
}
7
else {
result = 1;
}
}
[8-2] In old versions of FORTRAN, you could only use one statement
in the if-clause (right after the ‘if’ keyword and the condition). Explain why this was a bad design choice and how the problem was
solved in C.
1. Poor Readability – Complex conditions required multiple nested IF statements, making code hard to read.
2. Lack of Code Block Support – Developers had to repeat IF statements to
execute multiple lines, leading to redundancy.
3. Error-Prone Modifications – Adding new actions required rewriting the
structure, increasing chances of bugs.
• solved in C by adding Curly brackets.
[9-1] In most Fortran IV implementations, parameters were passed
by reference, using access path transmission only. State both the
advantages and disadvantages of this design choice.
Advantages of Pass-by-Reference in Fortran IV
1. Efficiency in Memory Usage
• Since only the memory address is passed, no extra memory is required
to copy the value.
• This is particularly beneficial for large arrays or complex data structures.
2. Efficiency in Execution Time
• Avoids the overhead of copying large data structures into function
arguments.
• Faster function calls, especially with large arrays or matrices used in
scientific computations.
3. Allows Functions to Modify Arguments
• Functions can modify the original variables passed to them, making
it easy to return multiple results.
4. Useful for Returning Multiple Values
• Since arguments are references, subroutines can modify multiple variables, eliminating the need for multiple return values.
8
Disadvantages of Pass-by-Reference in Fortran IV
1. Unintended Side Effects (Aliasing Problem)
• Since arguments are references, modifying one parameter may unintentionally change another if they reference the same memory.
2. Lack of Safety
• No protection against accidental modifications of passed parameters.
• This makes the code harder to reason about and maintain.
3. Harder to Optimize
• Since parameters can be modified anywhere in a function, compilers
struggle to optimize the code.
• Modern compilers use constant propagation and register optimization, which is harder with pass-by-reference.
4. Potential for Memory Corruption
• If a function accidentally modifies memory outside the intended scope,
it can lead to unexpected behavior or crashes.
[9-2] Consider the following program written in C syntax:
void swap(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
void main() {
int value = 2, list[5] = {1, 3, 5, 7, 9};
swap(value, list[0]);
swap(list[0], list[1]);
swap(value, list[value]);
}
For each of the following parameter-passing methods, what are all of
the values of the variables value and list after each of the three calls
to swap?
a. Passed by value
b. Passed by reference
c. Passed by value-result
9
a. Case (a): Pass-by-Value
value = 2
list = {1, 3, 5, 7, 9}
Case (b): Pass-by-Reference
value = 2
list = {3, 1, 5, 7, 9}
Case (c): Pass-by-Value-Result
value = 2
list = {3, 1, 5, 7, 9}
[9-3] Present one argument against providing both static and dynamic
local variables in subprograms.
• In subprograms local variables can be static or dynamic; If local variable
treated statically: This allows for compile-time allocation/ deallocation
and ensures proper type checking but does not allow for recursion. And
if local variables treated dynamically: This allows for recursion at the
cost of run-time allocation/ deallocation and initialization because these
are stored on a stack, referencing is indirect based on stack position and
possibly time consuming
[9-4] Consider the following program written in C syntax:
void fun (int first, int second) {
first += first;
second += second;
}
void main() {
int list[2] = {1, 3};
fun(list[0], list[1]);
}
For each of the following parameter-passing methods, what are the
values of the list array after execution?
a. Passed by value
b. Passed by reference
c. Passed by value-result
10
Case (a): Pass-by-Value
list[0] = 1
list[1] = 3
Case (b): Pass-by-Reference
list[0] = 2
list[1] = 6
Case (c): Pass-by-Value-Result
list[0] = 2
list[1] = 6
11