Sunday, January 7, 2018

Software Engineering interview questions - 2

Q. Implement memcpy.

A.
void *memcpy (void *dest, const void *src, size_t n)
{
    char *dp = dest;
    const char *sp = src;
    while (n--)
        *dp++ = *sp++;
    return dest;
}


Note: In C99, the prototype is:
void *memcpy (void * restrict dest, const void * restrict src, size_t n)



Q. What is a default constructor?

Q. Consider a base class A and derived class B; which data members of A will be visible to B?
A. The public and protected members.

Q. int x = 5;
   x++++;
   What will the value of x be?
   
A. Statement will cause syntax error.

Q. Which of these data structures have the least access time for any element:
   Linked list, hash table, array and ?? 
A. Hash table and array.

Q. What is the difference between a struct and a class? 
A. A struct contains only data.
   Class, used in object oriented programming only, contains data as well as methods to access the data. Class can contain struct.
   
Q. What is the default visibility of a struct and a class?
A. struct is global; class is private by default.

Note that it really refers to members of the class/struct.
Also it's actually the accessibility, different from visibility. 
(based on C++ standard) 
Names of members are still visible, and implicit conversions to base classes are still considered, when those members and base classes are inaccessible.

Q. What are the methods a compiler provides a class by default?
A. Default constructor, copy constructor, destructor, copy assignment.

(based on 'Effective C++' by Scott Meyers)
Functions C++ silently writes and calls:
If you write this:
class Empty{};
it's the same as if you'd written this:

class Empty {
public:
Empty();
Empty (const Empty &);
~Empty();
Empty& operator=(const Empty& rhs);
};

Q. 40 >> 2; what do we get?
A. 0100 0000 >> 2 => 0001 0000 = 10 (hex)

Q. How can we get a program to print "Hello world" before main() runs?
A. print statement inside a class constructor.

Q. What is a virtual function?

Q. What is the difference between new and malloc?
A. 
(based on Scott Meyers's book 'Effective C++')
Ex:
string *stringArray1 = static_cast<string*> (malloc (10 * sizeof (string));
string *stringArray2 = new string[10];

stringArray1 points to enough memory for 10 string objects, but no objects have been constructed in that memory. Furthermore without jumping through some rather obscure linguistic hoops, you have no way to initialize the objects in that array. In contrast, stringArray2 points to 10 fully constructed string objects, each of which can safely be used in any operation taking a string
.

Nonetheless, let's suppose you magically managed to initialize the objects in the stringArray1 array. Later on in your program, then, you'd expect to do this:
free(stringArray1);
delete [] stringArray2;

The call to free will release the memory pointed to by stringArray1, but no destructors will be called on the string objects in that memory. If the string objects themselves allocated memory, all the memory they allocated will be lost. On the other hand, when delete is called on stringArray2, a destructor is called for each object in the array before any memory is released.

Because new and delete interact properly with constructors and destructors, they are clearly the superior choice.

Q. What is the difference between a thread and a process?
A. 
(based on 'Understanding the Linux Kernel' (O'Reilly series) )
A process is an instance of a program in execution.
In multithreaded applications, a process is composed of several threads, each of which represents an execution flow of the process.

(based on 'Operating System Concepts' by Silberschatz, Galvin, Gagne, Ch. 5 - Threads)
A thread, sometimes called a lightweight process (LWP), is a basic unit of CPU utilization; it comprises a thread ID, a program counter, a register set, and a stack. It shares with other threads belonging to the same process its code section, data section and other operating-system resources, such as open files and signals. A traditional (or heavyweight) process has a single thread of control. If the process has multiple threads of control, it can do more than one task at a time.


Q. What is a zombie process?
A. (based on 'Linux System Programming' (O'Reilly series) by Robert Love)

(P. 162)
A process that has terminated but has not yet been waited upon by its parent. Zombie processes continue to consume system resources, although only a small percentage. These resources remain so that parent processes that want to check up on the status of their children can obtain information relating to the life and termination of these processes. Once the parent does so, the kernel cleans up the process for good and the zombie ceases to exist.

(P. 18)
When a process terminates, it is not immediately removed from the system. Instead the kernel keeps parts of the process resident in memory to allow the process's parent to inquire about its status upon terminating. This inquiry is known as 'waiting on' the terminated process. Once the parent process has waited on its terminated child, the child is fully destroyed.

Q. How does combination of functions reduce memory requirements in systems?


Q. What is the need for an infinite loop in systems?


Q. A vast majority of High Performance systems today use RISC architecture why?
A. (based on 'ARM System Developer's Guide')
The RISC philosophy concentrates on reducing the complexity of instructions performed by the hardware because it is easier to provide greater flexibility and intelligence in software than hardware. As a result, a RISC design places greater demands on the compiler. In contrast, the traditional CISC computer
relies more on the hardware for instruction functionality, and consequently the CISC instructions are more complicated.

RISC philosophy is implemented with 4 major design rules:
1. Instructions - RISC processors have a reduced number of instruction classes. These classes provide simple operations that can each execute in a single cycle.
Each instruction is a fixed length to allow the pipeline to fetch future instructions before decoding the current instruction. In contrast, in CISC processors the instructions are often of variable size and take many cycles to execute.
2. Pipelines - There is no need for an instruction to be executed by microcode as in CISC processors.
3. Registers - RISC machines have a large general-purpose register set. In contrast, CISC processors have dedicated registers for specific purposes.
4. Load-store architecture - Memory accesses are costly, so separating memory accesses from data processing provides an advantage. In contrast, with a CISC design the data processing operations can act on memory directly.


Q. Why do we need virtual device drivers when we have physical device drivers?

Q. What is the need for DMAC?
A. (based on 'Embedded Software')
Direct memory access (DMA) is a mechanism for accessing memory without the intervention of the CPU. A peripheral device (the DMA controller) is used to write data directly to and from memory.
The DMAC is just another type of CPU whose only function is moving data around very quickly. The DMAC moves the data in parallel with the CPU operation, and notifies the CPU when the transfer is complete.

DMA is most useful for copying larger blocks of data. Smaller blocks of data do not have the payoff because the setup and overhead time for the DMA makes it worthwhile just to use the CPU.


Q. What is Endianness of a system and how do different systems communicate with each other?

(based on "Communication Networks" by Leon-Garcia, Widjaja (Ch. 2))
Different computers may store a multibyte word in different orders. If the least significant byte is stored first (has lower address), it is known as little endian. If the most significant byte is stored first, it is known as big endian.
For any two computers to be able to communicate, they must agree on a common data format while transferring multibyte words. The Internet adopts the big-endian format. The representation is known as network byte order in contrast to the representation adopted by the host, which is called host byte order. It is important to remember that the values of sin_port and sin_addr must be in the network byte order, since these values are communicated across the network. Four functions are available to convert between the host and network byte order conveniently - htons, htonl, ntohs, ntohl. We need to use these functions so that programs will be portable to any machine.

Q. How are macros different from inline functions?


Q. What could be the reasons for a System to have gone blank and how would you Debug it?


Q. Explain interrupt latency and how can we decrease it?
A. (based on 'ARM System Developer's Guide')
The interval of time from an external interrupt request signal being raised to the first fetch of an instruction of a specific interrupt service routine (ISR).

Interrupt latency depends on a combination of hardware and software.

Software handlers have 2 main methods to minimize interrupt latency.
The 1st method is to use a nested interrupt handler, which allows further interrupts to occur even when currently servicing an existing interrupt. This is achieved by reenabling the interrupts as soon as the interrupt source has been serviced(so it won’t generate more interrupts) but before the interrupt handling is complete. Once a nested interrupt has been serviced, control is relinquished to the original ISR.
The 2nd method involves prioritization. You program the interrupt controller to ignore interrupts of the same or lower priority than the interrupt you are handling, so only a higher-priority task can interrupt your handler. You then reenable interrupts.


Q. How to create a child process in Linux?
A. (based on 'Understanding the Linux Kernel' (O'Reilly series))
In traditional Unix systems, resources owned by the parent process are duplicated in the child process. This approach makes the process creation slow and inefficient, since it requires copying the entire address space of the parent process.

Modern Unix kernels solve this problem by introducing 3 different 
mechanisms:
- Copy On Write technique allows both the parent and child to read the same physical pages. Whenever either one tries to write on a physical page, the kernel copies its contents into a new physical page that is assigned to the writing process.
- Lightweight processes allow both the parent and child to share many per-process kernel data structures
- vfork() system call creates a process that shares the memory address space of its parent. To prevent the parent from overwriting data needed by the child, the parent's execution is blocked until the child exits or executes a new program.

Lightweight processes are created in Linux by using a function named clone().

The traditional fork() system call is implemented by Linux as a clone() system call, whose flags parameter specifies both a SIGCHLD signal and all the clone flags cleared, and whose child_stack parameter is 0.
The vfork() system call is implemented by Linux as a clone() system call whose first parameter specifies both a SIGCHLD signal and the flags CLONE_VM and CLONE_VFORK, and whose 2nd parameter is 0.

(based on 'Linux Programmer's Toolbox' by John Fusco, Ch 6 - Understanding Processes)
Linux creates processes with one of 3 system calls. Two of these are traditional system calls provided by other UNIX variants: fork and vfork. The third is Linux specific and can create threads as well as processes. This is the clone system call.

When fork returns, there will be two processes: a parent and child, identical clones of each other. fork returns a process ID (pid_t) that will be either zero or nonzero. From the programmer's perspective, the only difference between parent and child is the value returned by the fork function. The parent sees a nonzero return value, which is the process ID of its child process (or -1 if there's an error). The child sees a zero return value, which indicates that it is the child. What happens next is up to the application. The most common pattern is to call one of the exec system calls, although that is by no means required.

The vfork system call is something of an artifact. It is virtually identical to fork except that vfork guarantees that the user-space memory will not be copied. A fork call would cause all the process's user-space memory to be copied into new pages. This is especially wasteful if the only thing the child process is going to do is call exec.

The clone system call is unique to Linux and can be used to create processes or threads. Portable code should never use the clone system call. POSIX APIs should be sufficient enough to provide what you need, be it a thread or a process.
clone is a complicated system call implemented as kind of a general-purpose fork. It gives the application full control over which parts of the child process will be shared with the parent. This makes it suitable for creating processes or threads. You can think of a thread as being a special-case process that shares its user space with its parent.

(based on 'Operating System Concepts' by Silberschatz, Galvin, Gagne, Ch. 5 - Threads)
A set of flags is passed as a parameter to the clone system call. This set of flags is used to indicate how much of the parent process is to be shared with the child. If none of the flags is set, no sharing occurs and clone acts just like fork. If all five flags are set, the child process shares everything with the parent. Other combinations of the flags allow various levels of sharing between these two extremes.
Interestingly, Linux does not distinguish between processes and threads. In fact, Linux generally uses the term task - rather than process or thread - when referring to a flow of control within a program. Aside from the cloned process, Linux does not support multithreading, separate data structures, or kernel routines. However various Pthreads implementations are available for user-level multithreading.

Q. Significance of watchdog timer
A. (based on 'Building Embedded Linux Systems' (O'Reilly series))
Often, failure detection and recovery is done by means of system monitoring hardware and software such as watchdogs.
There are both hardware and software implementations of watchdog timers. Watchdog timers depend on periodic reinitialization so as not to reboot the system. If the system hangs, the timer eventually expires and causes a reboot.


Q. If you buy some OS, what are the features you look for in?


Q. Why is java mostly used in systems?


Q. Differentiate between mutexes vs semaphores


Q. What is the need for having multibyte data input and output buffers in case of device ports?


Q. What is polymorphism?


Q. What is a pure virtual function?


Q. For a class to be abstract, do all virtual functions need to be pure?


Q. What are the advantages of C++?


Q. In function overloading, is there a restriction on the return type?


Q. Have you done multiple inheritance?

A.
(based on 'Object-Oriented Programming and Design', Ch 5 - 'Designing Class Hierarchies')
Multiple inheritance enables the designes to create objects that are closer to their reality. A fax modem card is essentially a modem and a fax combined in one. Similarly, a fax_modem class that is publicly derived from both fax and modem represents the concept of a fax/modem better than a single inheritance model does. But the most compelling argument in favor of multiple inheritance is that some designs cannot be realized without it. For example, implementing the Observer pattern in Java is nearly impossible because Java lacks multiple inheritance.

(based on 'Effective C++', Item #26)
Multiple inheritance is rife with possibilities for potential ambiguity. The most straightforward case occurs when a derived class inherits the same member name from more than one base class:

class Base1 {
public:
    int doIt();
};

class Base2 {
public:
    void doIt();
};

class Derived: public Base1,     // Derived doesn't declare a function called doIt
                        public Base2  {
....
};

Derived d;
d.doIt();    // error! - ambiguous

When class Derived inherits two functions with the same name, C++ utters not a whimper; at this point the ambiguity is only potential. However, the call to doIt forces compilers to face the issue, and unless you explicitly disambiguate the call by specifying which base class function you want, the call is an error:

d.Base1::doIt();
d.Base2::doIt();

(based on 'Object-Oriented Programming and Design', Ch 5 - 'Designing Class Hierarchies')
Multiple inheritance can lead to a problem known as the DDD (or dreadful diamond of derivation).

class ElectricAppliance {
private:
    int voltage;
    int Hertz;
public:
    // constructor and other useful methods
    int getVoltage () const {return voltage;}
    int getHertz () const (return Hertz;}
};

class Radio: public ElectricAppliance {/* ...*/};
class Tape: public ElectricAppliance {/* ... */};
class RadioTape: public Radio, public Tape {/* ... */};

int main()
{
    RadioTape rt;
    // Compilation error - ambiguous error.
    // Two copies of getVoltage()  exist in rt: one from Radio and one from Tape
    int voltage = rt.getVoltage();
    return 0;
}

In such cases, where reduplication of data and methods from a common base class is undesirable, use virtual inheritance.

class Radio: virtual public ElectricAppliance {/* ...*/};
class Tape: virtual public ElectricAppliance {/* ... */};

Q. If you are designing a system, how would you decide if you should use C or C++?


Q. You have 2 devices that are not talking to one another. You have a terminal where you can go check the status of these devices. What would you look for?
A. They have to be on the same subnet.

Q. (related to previous) Besides the subnet, what is the other component?


Q. (white board programming) Linked list implementation in C++.

Q. (interviewer draws a diagram depicting a real life scenario in their data centers - ) 2 sets of nodes, each on a different VLAN, and a different subnet, let's say 10.1.X.X and 10.2.X.X respectively. How can you ping from one set of nodes to the other?

A.
(based on 'Using UNIX')
Static routes are fixed entries in the routing table. These routes are added to the routing table when an interface on your local host becomes available, or the system administrator uses the route command to manually add a route.

route add [host | net] destination gateway metric
e.g.
$ route add host 199.71.122.51 10.10.1.5 1



Q. One server can be reached via ping, but you cannot do an SSH into it. You can walk up to it and open a terminal to it. What do you investigate?


Q. (white board programming) Reverse a string in C.
A. (based on K&R book)

/* reverse: reverse string s in place */
void reverse (char s [])
{
    int c,i,j;
    for (i = 0, j = strlen (s) - 1; i<j; i++, j--)
    {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}


Q. TCP v UDP.


Q. What is network bonding?
A. (based on 'Understanding Linux Network Internals')
Bonding, also called EtherChannel (Cisco terminology) and trunking (Sun terminology), allows a set of interfaces to be grouped together and be treated as a single interface. This feature is useful when a system needs to support point-to-point connections at a high bandwidth. A nearly linear speedup can be achieved, with the virtual interface having a throughput nearly equal to the sum of the throughputs of the individual interfaces.

Virtual Devices.
A virtual device is an abstraction built on top of one or more real devices. Linux allows you to define different kinds of virtual devices.
Bonding - with this feature, a virtual device bundles a group of physical devices and makes them behave as one.

Q. What command would you use in Linux to find ports that are listening?

Q. Can you explain the difference between a shallow copy and a deep copy?


Q. In case we have one string object assigned to another for simplicity, how do we get around the problem of dangling pointer when one goes out of scope?
(i.e. in a scenario when we DO wanna do shallow copy)

A. (based on book by Stephen Prata)
Smart Pointer considerations (Pg. 973)
Why three Smart Pointers? ... And why is auto_ptr being deprecated?

Begin by considering assignment,
auto_ptr <String> ps (new String ("I reigned lonely as a cloud."));
auto_ptr <String> vocation;
vocation = ps;

What should the assignment statement accomplish? If ps and vocation were ordinary pointers the result would be two pointers pointing to the same String object. That is not acceptable here because the program would wind up attempting to delete the same object twice - once when ps expires, and once when vocation expires. There are ways to avoid this problem.

- Define the assignment operator so that it makes a deep copy. This results in two pointers pointing to two distinct objects, one of which is a copy of the other.
- Institute the concept of ownership, with only one smart pointer allowed to own a particular object. Only if the smart pointer owns the object will its destructor delete the object. Then have assignment transfer ownership. This is the strategy used for  auto_ptr and for unique_ptr, although unique_ptr is somewhat more restrictive.

- Create an even smarter pointer that keeps track of how many smart pointers refer to a particular object. This is called reference counting. Assignment, for example, would increase the count by one and the expiration of a pointer would decrease the count by one. Only when the final pointer expires would delete be invoked. This is the shared_ptr strategy.

The same strategies we've discussed for assignment, of course, would also apply to the copy constructors.


Q. Are you familiar with any C++ container classes?

No comments:

Post a Comment