C Variables & Data Types - Tricky MCQ

Previous C Variables, Data Types & Keywords Next

Tricky Questions on Variables, Data Types & Keywords

1

What is the size of 'void' data type in C?

Correct Answer: C) Compilation error - cannot use sizeof on void

The 'void' type is an incomplete type that cannot be completed. In C, sizeof(void) is invalid and results in a compilation error. However, GCC allows it as an extension and returns 1. The void type can only be used in specific contexts: function return types, function parameters (void), and pointers to void (void*).

// This will give compilation error in standard C: // printf("%zu", sizeof(void)); // INVALID
2

What is the difference between "auto" and "register" storage classes?

Correct Answer: D) Both A and B are correct

'auto' is the default storage class for local variables (stored in stack memory). 'register' is a hint to the compiler to store the variable in CPU registers for faster access, but it's only a request - the compiler may ignore it. You cannot take the address of a register variable (& operator). In modern compilers with optimization, 'register' is often ignored as compilers are better at register allocation.

3

What is the output: printf("%d", sizeof(3.14));

Correct Answer: B) 8 (double size)

In C, floating-point constants like 3.14 are of type double by default, not float. To make it a float, you need to append 'f' or 'F' (3.14f). Similarly, 3.14L would be long double. So sizeof(3.14) returns the size of double (typically 8 bytes on most systems).

printf("%zu", sizeof(3.14)); // Output: 8 (double) printf("%zu", sizeof(3.14f)); // Output: 4 (float) printf("%zu", sizeof(3.14L)); // Output: 12 or 16 (long double)
4

Which of these is NOT a valid variable name in C?

Correct Answer: D) All are invalid as variable names

All of these are keywords in C and cannot be used as variable names. 'int' and 'register' are keywords in all C versions. '_Bool' was introduced as a keyword in C99. Keywords are reserved words that have special meaning to the compiler and cannot be used as identifiers.

// All of these will cause compilation errors: // int int = 10; // ERROR: 'int' is a keyword // int register = 20; // ERROR: 'register' is a keyword // _Bool _Bool = 1; // ERROR: '_Bool' is a keyword (C99+)
5

What is the range of 'char' data type in C?

Correct Answer: C) Implementation-defined (can be signed or unsigned)

This is a tricky one! The C standard says plain 'char' can be either signed or unsigned, depending on the implementation (compiler and platform). Most compilers treat char as signed by default, but ARM compilers often treat it as unsigned. To guarantee signedness, use 'signed char' or 'unsigned char' explicitly.

char c = 255; // May be -1 (signed) or 255 (unsigned) // Better to be explicit: signed char sc = 255; // Will be -1 (overflow) unsigned char uc = 255; // Will be 255
6

What is "variable shadowing" in C?

Correct Answer: B) When a local variable hides a variable in outer scope

Variable shadowing occurs when a variable declared in an inner scope (like inside a function or block) has the same name as a variable in an outer scope (like a global variable or function parameter). The inner variable "shadows" or hides the outer variable, making it inaccessible within that scope.

int x = 10; // Global variable void func() { int x = 20; // Local variable shadows global x printf("%d", x); // Prints 20, not 10 { int x = 30; // Shadows both outer x variables printf("%d", x); // Prints 30 } }
7

What is the difference between "const" and "#define" for constants?

Correct Answer: D) All of the above

const creates typed constants that occupy memory (like variables), have scope, and provide type safety. #define creates text substitutions (macros) that don't use memory, have no type checking, and are replaced by the preprocessor before compilation. const is generally preferred in modern C for type safety and debugging.

#define PI 3.14159 // Text substitution, no type const float pi = 3.14159; // Typed constant, uses memory // With #define: #define SIZE 10 int arr[SIZE]; // Works - replaced before compilation // With const (in C89): const int size = 10; int arr[size]; // ERROR in C89 (not a constant expression) // Works in C99 with VLAs or if size is compile-time constant
8

What is the value of: sizeof('A' + 1);

Correct Answer: C) 4 (int size due to integer promotion)

In C, character constants like 'A' are of type int. When you add 1 to it, the result is still an int. More importantly, C has integer promotion: in expressions, char and short are promoted to int before operations. So sizeof('A' + 1) returns sizeof(int), typically 4 bytes.

char c = 'A'; printf("%zu", sizeof(c + 1)); // Output: 4 (int) printf("%zu", sizeof(c)); // Output: 1 (char) printf("%zu", sizeof('A')); // Output: 4 (int in C, 1 in C++)
9

Which keyword makes a variable retain its value between function calls?

Correct Answer: A) static

The static keyword, when applied to a local variable inside a function, gives it static storage duration. This means the variable persists between function calls and retains its value. It's initialized only once (to 0 if no explicit initializer) and exists for the lifetime of the program.

void counter() { static int count = 0; // Initialized only once count++; printf("%d ", count); } counter(); // Prints: 1 counter(); // Prints: 2 counter(); // Prints: 3 // 'count' retains its value between calls
10

What is the output: int a = 5, b = 2; float c = a / b; printf("%f", c);

Correct Answer: B) 2.0 (integer division then conversion)

In C, when both operands of division are integers, integer division occurs (truncates toward zero). So 5/2 = 2 (not 2.5). This integer result (2) is then converted to float (2.0) when assigned to c. To get float division, make at least one operand float: float c = (float)a / b; or float c = a / 2.0;

int a = 5, b = 2; float c = a / b; // Integer division: 5/2 = 2, then 2.0 printf("%f", c); // Output: 2.000000 float d = (float)a / b; // Float division: 5.0/2 = 2.5 printf("%f", d); // Output: 2.500000
11

What does the "restrict" keyword do in C (C99)?

Correct Answer: B) Optimization hint that pointers don't alias

The restrict keyword (C99) is a type qualifier for pointers. It tells the compiler that for the lifetime of the pointer, only that pointer (or directly derived from it) will access the pointed-to object (no aliasing). This allows more aggressive optimizations. Violating this promise leads to undefined behavior.

void copy(int *restrict dest, int *restrict src, int n) { // Compiler can optimize assuming dest and src don't overlap for (int i = 0; i < n; i++) dest[i] = src[i]; } // With restrict, compiler might use vectorized instructions // Without restrict, must handle potential overlap
12

What is "type punning" in C and is it safe?

Correct Answer: B) Accessing memory as different type - unsafe, violates strict aliasing

Type punning is accessing the same memory location as different types (e.g., accessing an int as float). This violates C's strict aliasing rule which says you can only access an object through its own type, a compatible type, or a character type. Violating this leads to undefined behavior. The safe way is using memcpy() or unions (in C99).

// UNSAFE type punning (violates strict aliasing): int x = 0x3f800000; float f = *(float*)&x; // UNDEFINED BEHAVIOR! // SAFE using memcpy: int x = 0x3f800000; float f; memcpy(&f, &x, sizeof(f)); // OK // SAFE using union (C99): union { int i; float f; } u; u.i = 0x3f800000; float f = u.f; // OK in C99 (type punning allowed via union)
13

What is the "volatile" qualifier used for?

Correct Answer: D) All of the above

volatile tells the compiler that a variable's value may change at any time without any action being taken by the code. This prevents optimization (like caching in register). Used for: 1) Memory-mapped hardware registers, 2) Variables shared between main code and interrupt service routines, 3) Variables shared between threads (though not sufficient for thread safety - needs atomic operations or mutexes), 4) Variables accessed in signal handlers.

// Hardware register example: volatile uint32_t *status_reg = (uint32_t*)0x40021000; while (*status_reg & 0x01) { // Compiler will read from memory each time // Wait for bit 0 to clear } // Without volatile, compiler might optimize to: // uint32_t temp = *status_reg; // while (temp & 0x01) { ... } // Infinite loop if bit was set!
14

What is the difference between "signed" and "unsigned" integer overflow?

Correct Answer: B) Signed overflow is UB, unsigned wraps modulo 2^n

In C, signed integer overflow is undefined behavior - anything can happen (crash, wrong results, etc.). Unsigned integer overflow (or more precisely, "wrap-around") is well-defined: it wraps modulo 2^n (where n is number of bits). This is a critical difference for security and optimization.

// Signed overflow - UNDEFINED BEHAVIOR: int x = INT_MAX; x = x + 1; // UB! Compiler might optimize away checks // Unsigned overflow - Well-defined wrap-around: unsigned int y = UINT_MAX; y = y + 1; // Well-defined: y becomes 0 // Example of optimization due to UB: int check_overflow(int a, int b) { if (a + b < a) // Compiler might optimize this away! return 1; // Because a+b < a can't happen with defined behavior return 0; }
15

What is "flexible array member" in C (C99)?

Correct Answer: B) Array declared with [] as last member of struct

A flexible array member (C99) is an array without a specified size as the last member of a struct. It allows allocating memory for the struct plus additional array elements dynamically. This replaces the old "struct hack" (array of size 1). The struct must have at least one other member before the flexible array.

// Flexible array member (C99): struct packet { int length; char data[]; // Flexible array member }; // Allocation: struct packet *p = malloc(sizeof(struct packet) + 100); p->length = 100; // p->data[0] to p->data[99] are valid // Old "struct hack" (pre-C99, not strictly conforming): struct old_packet { int length; char data[1]; // Actually allocate more };
Previous C Variables, Data Types & Keywords Next