C++ Core Concepts & Memory Handling Interview Questions
C++ is a mid-level language combining high-level abstractions (classes, inheritance, templates) with low-level memory control (pointers, dynamic allocation). Core concepts include variables, functions, references, operators, and object-oriented features like encapsulation and polymorphism. Memory handling covers stack vs heap allocation, pointers, dynamic memory (new/delete), deep vs shallow copies, and smart pointers.
1. How do you free memory allocated with new?
Use delete for single values, and delete[] for arrays:
#include <iostream>
using namespace std;
int main() {
int* p = new int;
delete p;
int* arr = new int[10];
delete[] arr;
return 0;
}
2. What is the difference between new and malloc()?
Following are the major difference between new and malloc()
new | malloc() |
---|---|
An operator which performs an operation | A function that returns and accepts values |
Calls the constructors | Cannot call a constructor |
Returns the exact data type | Returns void* |
3. What happens if we change pointer ?
If you pass a pointer by value but forget to use dereferencing, changes wonât reflect.
Code:
#include <iostream>
using namespace std;
void wrongUpdate(int *p) {
int x = 100;
p = &x; // Changing the pointer locally, not the original address
}
int main() {
int a = 10;
int *ptr = &a;
wrongUpdate(ptr);
cout << "Value: " << *ptr << endl; // Will still print 10
return 0;
}
4. How internally delete and delete[] works in cpp?
delete and delete[] deallocate memory obtained from new and new[].
- For single objects, delete calls the destructor (if any) and then releases memory using operator delete.
- For arrays, delete[] calls the destructor for each element and then uses operator delete[] to free the memory block.
The compiler typically keeps track of the number of elements internally (implementation-defined).
Note: The actual destructor calls and memory release are handled automatically by the compiler/runtime; programmers should never explicitly invoke ptr->~T() before delete.
Example of delete:
ptr->~T(); // Call destructor
operator delete(ptr); // Free memory
Example of delete[]:
for (int i = size - 1; i >= 0; --i)
arr[i].~T(); // Call destructor on each
operator delete[](arr); // Free memory
Note: If the object is a primitive type (int, char), destructors aren't needed.
5. How we can make custom delete?
You can overload operator delete or operator delete[] in your class:
void operator delete(void* ptr) {
std::cout << "Custom delete\n";
::operator delete(ptr);
}
6. What happens if you return a pointer to a local variable from a function?
The pointer becomes dangling because the variableâs lifetime ends when the function exits. Accessing it causes undefined behavior.
int* foo() {
int x = 10;
return &x;
}
7. Demonstrate shallow vs deep copy in C++ with memory implications.
#include <iostream>
using namespace std;
int main() {
// Shallow Copy
int *a = new int(10);
int *b = a; // shallow copy: both point to same memory
*b = 20; // changing b also changes a
cout << "Shallow Copy:" << endl;
cout << "a = " << *a << ", b = " << *b << endl; // both 20
// Deep Copy
int *x = new int(10);
int *y = new int(*x); // deep copy: new memory with same value
*y = 30; // changing y does not affect x
cout << "\nDeep Copy:" << endl;
cout << "x = " << *x << ", y = " << *y << endl; // x=10, y=30
delete a; // free memory
// delete b; // dangerous in shallow copy (same memory as a)
delete x;
delete y;
return 0;
}
Output
Shallow Copy: a = 20, b = 20 Deep Copy: x = 10, y = 30
- Shallow Copy: Both pointers (a and b) refer to the same memory. Updating one updates the other.
- Deep Copy: A new memory block is allocated (y), so changes donât affect the original (x).
8. Explain why delete cannot be used for arrays with demonstration?
- If memory is allocated using new[], it must be freed using delete[].
- Using plain delete on an array leads to undefined behavior because the compiler will not correctly destroy all elements.
- For simple data types (like int), it may look like it works, but for objects with destructors, only the first element may be destroyed, leaving the rest as a memory leak.
- Rule of thumb: 1) new â delete, 2)new[] â delete[]
Example:
#include <iostream>
using namespace std;
int main() {
int* arr = new int[3]{1, 2, 3};
delete arr; // Wrong: only first element destructor called
// delete[] arr; // Correct: destructors for all elements
return 0;
}
9. Show why base class destructors should be virtual.?
- If the base class destructor is not virtual, deleting a derived object through a base pointer calls only the base destructor.
- This skips cleanup of the derived class â can cause memory leaks or incomplete destruction.
- Making the destructor virtual ensures the derived destructor is called first, then the base destructor â proper cleanup.
Example:
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base constructor\n"; }
~Base() { cout << "Base destructor\n"; } // Non-virtual
};
class Derived : public Base {
public:
Derived() { cout << "Derived constructor\n"; }
~Derived() { cout << "Derived destructor\n"; }
};
int main() {
Base* obj = new Derived();
delete obj; // Derived destructor not called: memory leak
return 0;
}
Output
Base constructor Derived constructor Base destructor
10. How do auto and decltype improve type safety? Show with example.
- auto deduces the type from the initializer, ensuring the variable always matches the assigned expression and avoiding mismatched type declarations.
- decltype deduces the type from an expression or variable, ensuring consistency in return types and variable declarations.
- Together, they improve type safety by letting the compiler handle type correctness, especially in complex or generic code.
Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
// auto deduces type
auto it = v.begin(); // deduced as vector<int>::iterator
cout << *it << endl;
// decltype deduces type of expression
decltype(v)::iterator it2 = v.end();
cout << *(--it2) << endl;
return 0;
}
Output
1 3
12. Show how mutable allows changing internal state even in a const object?
- Normally, a const object in C++ cannot modify its data members.
- If a data member is marked mutable, it can still be changed even inside a const object.
- This is useful when some internal details (like caching, counters, or logs) need to change without affecting the logical state of the object.
Example:
#include <iostream>
using namespace std;
class Example {
mutable int accessCount; // can be modified even in const object
public:
Example() : accessCount(0) {}
void show() const {
accessCount++; // allowed because 'accessCount' is mutable
cout << "Access count = " << accessCount << endl;
}
};
int main() {
const Example obj; // const object
obj.show(); // modifies mutable member
obj.show();
}
Output
Access count = 1 Access count = 2
Even though obj is const, the mutable member (accessCount) can still be changed.
13. How does pointer arithmetic lead to undefined behavior if misused?
- Pointer arithmetic allows direct memory access, but accessing out-of-bounds memory is undefined.
- Compiler may not throw errors, but program may crash or show garbage.
Example cases:
- Incrementing past the end of an array.
- Decrementing before the start of an array.
- Adding large offsets that go far beyond allocated memory.
Code Example:
#include <iostream>
using namespace std;
int main() {
int arr[3] = {1, 2, 3};
int* ptr = arr;
cout << *(ptr + 2) << endl; // valid, outputs 3
cout << *(ptr + 5) << endl; // Undefined behavior
return 0;
}
Output
3 1587343946
14. Demonstrate difference in memory layout and access.
- int* arrOfPtrs[3]: An array of 3 pointers to int. Each pointer can point to separate memory locations (may be on stack or heap).
- int (*ptrToArr)[3]: A single pointer to an array of 3 ints. In the example, new int[1][3] allocates a block of 3 contiguous ints on the heap, and ptrToArr points to it.
So the difference is in layout:
- Array of pointers: multiple independent ints, not guaranteed contiguous.
- Pointer to array: one block of contiguous memory.
Example:
#include <iostream>
using namespace std;
int main() {
int a = 1, b = 2, c = 3;
int* arrOfPtrs[3] = {&a, &b, &c}; // Array of pointers
int (*ptrToArr)[3] = new int[1][3]; // Pointer to array
(*ptrToArr)[0] = 10; (*ptrToArr)[1] = 20; (*ptrToArr)[2] = 30;
cout << *arrOfPtrs[1] << endl; // Output: 2
cout << (*ptrToArr)[1] << endl; // Output: 20
delete[] ptrToArr;
}
Output
2 20