Section 1

Preview this deck

Why do we need to use null characters to denote the end of a string? No string termination character is used in Java...

Front

Star 0%
Star 0%
Star 0%
Star 0%
Star 0%

0.0

0 reviews

5
0
4
0
3
0
2
0
1
0

Active users

1

All-time users

1

Favorites

0

Last updated

6 years ago

Date created

Mar 1, 2020

Cards (39)

Section 1

(39 cards)

Why do we need to use null characters to denote the end of a string? No string termination character is used in Java...

Front

In Java, arrays are objects and other data can be associated with them. Like the number of elements. In C, arrays are just addresses and don't have any meta-data associated with them. Hence, we have to use a termination character to provide a way to tell how long a string is (i.e. a known stopping point). Else, we only have a starting place and could end up visiting all the memory after it.

Back

Create the three macros indicated with comments. // Create a macro called PI with precision of two decimal places. #define // Create a macro called AREA_OF_SPHERE that takes in value radius and computes the surface area of a sphere. 4pir^2 #define // Create a macro called VOLUME_OF_CONE that takes in values radius, height and computes the volume of a cone. 1/3pir^2*h #define int radius = 3; int height = 5; float area = AREA_OF_SPHERE(radius); float volume = VOLUME_OF_CONE (radius, height);

Front

#define PI 3.14 #define AREA_OF_SPHERE(r) 4PIr #define VOLUME_OF_CONE(r,h) (1/3)PI(r^2)*h

Back

One of the primary jobs of an operating system is to manage resources (system memory, CPU time, mass storage, etc). Why is it important that the OS is responsible for this as opposed to having each individual application manage resources on their own? Explain.

Front

It ensures programs don't have free reign over lower level functions and thus makes it more difficult for such programs to do anything malicious (better security). It also takes the burden off of application developers to worry about making their program play nicely on all different system configurations with any number and types of other processes running (developer friendly). Finally, since the OS acts as a "middle man" of sorts, it can optimize what processes own what memory, and how long/frequently they're active on the CPU for (potentially more efficient).

Back

Consider the following small C program. Implement the function called toupper() which takes a C-style string and returns a version of it where lower case letters have been converted to uppercase. Assume that strings can only include letters and spaces. Hint: if there's a magic number you're looking for, it's probably 32. #include <stdio.h> void toupper(char* str) { //WRITE THIS CODE } void main() { char text[] = "Some sample text"; toupper(text); printf("%s", text);//prints "SOME SAMPLE TEXT" }

Front

int i = 0; while (str[i] != '/0') { if (str[i] >= 'a' && str[i] <= 'z') { &str[i] = &str[i] - 32; i++; } else { i++; } }

Back

What is the difference between global variables and static variables?

Front

Static variables are local to the functions they're defined in and can only be modified within that scope. They do retain value from scope coming in and out of scope. Global variables are global in the file and can be changed by anyone having access. They can be single file or multiple files. Scope is global to the program / file. A global variable can have the static keyword but will not change behavior as it is alive for the duration of the program.

Back

Why is it necessary to pass a pointer to an "object" as a parameter when dealing with an OOP style function? More specifically, what does C lack that other OOP languages have in regard to this and how does it impact being able to use and manipulate "objects" in C?

Front

C doesn't support classes and as such, there's no way to associate functions with a pointer. As such, any kind of function that does an operation on memory specified by a pointer needs to be passed as a parameter. Functionally you can perform the same operations on "objects" in C as you could in Java or C++, but you just have to pass the pointer as a parameter rather than using a dot operator on the object.

Back

Consider the operational differences between modular and microkernel operating systems. (a) Which design might provide better security and reliability? Explain (b) Which design might provide better performance? Explain.

Front

(a) The microkernel structure provides better security since information is exchanged via message passing through the kernel. This allows the operating system to regulate access to resources and to provide some mechanism for validating the authenticity of resource requests. Direct communication between modules may allow access to sensitive information if proper security measures are not implemented in each module. (b) A modular operating system might provide better performance, since messages can be exchanged directly between modules without the overhead of message passing through the microkernel. This also means that security and reliability are reduced with a modular operating system, since the kernel cannot moderate exchanges between modules.

Back

Exception in thread "main" invalid font data:at graphicsdriver.loadfont(nvcore.c: 23)at graphicsdriver.displaytext(nvcore.c: 54)at kernel.print(print: 232)at stdout.print(stdout.c:43)at cstdio.print(stdio.c:500)at cstdio.printf(stdio.c:243)at main.main(main.c:16) What structure (simple, layered, microkernel, or modular) was this kernel most likely developed with? Select a structure and justify it.

Front

layered

Back

logical error

Front

error in the way the program runs, resulting in incorrect or unexepected behavior ex ) 1 / 0 out of index loops

Back

Different operating system structures offer both benefits and drawbacks over one another, and it's important to understand what kind of structure should be used for different use-cases. Consider the following situation. You need to design an OS for a small system as a part of a contest. Due to the lack of time and scope of the project, you aren't overly concerned with the security or maintainability of the system, rather you just need the system to work, and do so efficiently. What structure (simple, layered, microkernel, or modular) should you choose for its kernel? Justify.

Front

A simple kernel structure would be best suited to this case because it should be the fastest of the bunch, and requires the least planning/design. Since maintainability and security aren't huge factors, you would just end up introducing overhead by choosing a layered or microkernel approach. Also, since the system is small, a modular kernel structure also probably isn't appropriate.

Back

Implement LinkedList: Implement list_create. list* list_create() { // TODO: IMPLEMENT }

Front

list* list_create() { stack newList = (list)malloc(sizeof(list)); if (newList == NULL) { printf("Failed to create"); exit(0); } else { newList->head = NULL; newList->size = 0; return newList; } }

Back

Implement LinkedList: Implement list_size void list_size(list* lst) {

Front

return lst->size;

Back

struct grade_node { int value; char assignment[255]; struct grade_node* next; }; struct grade_node* my_list = ... void remove_node() { if(my_list != NULL) my_list = my_list.next; } a) What is the syntax error in remove_node? b) What is the logical error in remove_node?

Front

a)The arrow operator should be used instead of the dot operator since my_list is a pointer. b) The memory for the node being removed is not freed.

Back

Managing limited system resources is an important task of the operating system. As mentioned on slide 14, a resource lock can be used to prevent two or more processes from accessing the same resource concurrently. Explain one potential problem with this approach and a potential solution for it.

Front

When one process acquires a lock on the resource, it prevents other processes from accessing that resource until it releases the lock. This could be addressed by placing a time limit on the lock, such that if the time limit expires the process is suspended and control of the resource is rotated to another process in the resource queue. A process may not check for the presence of a lock before acquiring the resource, which could inadvertently lead to the resource being used simultaneously by multiple processes. This could be addressed by relying on the operating system API for access to resources and establishing permissions preventing direct access to the resource from user-mode processes. If the process is interrupted before the lock is released the lock may remain indefinitely, preventing other processes from accessing the resource. This could be addressed by running a periodic task to verify that the processes associated with a locked resource are still alive.

Back

Consider the typical file structure of the C: (Windows) or / (Linux and macOS) drives. How is the hierarchy of this structure reflective of the permissions granted to various users within the system? Explain.

Front

In general, the file structure separates user program binaries and data files from system binary and configuration files. Individual home folders in the C:\Users or /home/ directories contain personal files and settings accessible only to that user. Windows also makes a distinction between 32 bit and 64 bit applications which are stored in the Program Files and Program Files (x86) directories. Since the file permissions are generally inherited from the parent directory, access to files tends to become more restrictive as you move further along the file tree.

Back

Using the template provided, implement the function 'insert_node' that traverses a SORTED, singly linked list (least to greatest), and inserts a node into the correct spot that keeps the list sorted. Make sure that the new node is added properly and the integrity of the list is maintained. You can assume that 'list' is already initialized with a list of custom_node's that contain sorted data values, with the address of 'list' pointing to the head of the list. You may also assume that the 'new_node' value will always be greater than the head of the list. struct custom_node { int data; struct custom_node* next; }; struct custom_node* list = ... void insert_node(struct custom_node* new_node) { //TODO: implement this.

Front

void insert_node(struct custom_node* new_node) { struct custom_node* prev; struct custom_node* tmp = list; while (tmp != NULL) { if (new_node->data < tmp->data) { new_node->next = tmp; } prev = tmp; tmp = tmp->next; } }

Back

Consider the following possible function prototypes for converting a color image to grayscale points: struct image bw_filter(struct image img); struct image bw_filter(struct image img); void bw_filter(struct image *img); Which of these functions should we expect to operate more efficiently and why?

Front

The third function should be expected to function most efficiently. In the first function, the execution environment has to make copies of the entire image to make it available as a local variable in the stack frame. In the second function, only the address must be copied; however, the program must still dynamically allocate memory for the resulting image in order to return a pointer to it. The third function can be expected to overwrite the input image, so no additional memory allocation operations are required.

Back

Consider the following two C programs: // Program 1 void main() { } // Program 2 int main() { return 0; } Conceptually, what is the difference between them?

Front

The first program isn't capable of generating an error status message, implying that it always works, while the second might (though: the code confirms that it terminates correctly, it is simply vacuous for the first)

Back

Identify four bugs and their types: #include <stdio.h> int main() { int result; char name[32]; char addr = name; printf("Enter your name:
"); scanf("%32s", &name); while (*(addr++)) { ++result; } printf("Your first name is % characters long.", result); return 0; }

Front

1. The var result is not initialized before it's used. Initialize result to 0. LOGICAL 2. scanf accepts up to 32 chars which is exactly the length of name - leaving no space for the null terminator. Increase the size of name or decrease the number of character accepted. LOGICAL 3. Reference operator & improperly used on name in scanf. name is already a pointer to the first element of the array, so referencing it could result in unpredictable behavior. Remove the ampersand before name. SYNTACTIC 4. The printf format specifier %, which prints nothing since it is incomplete. Change it to %d for an integer. SYNTACTIC

Back

Implement LinkedList: Define structs for a list and a list node.

Front

struct node { void* element; node* next; }; struct list { node* head; int size; };

Back

In Java, arrays are objects and other data can be associated with them. Like the number of elements. In C, arrays are just addresses and don't have any meta-data associated with them. Hence, we have to use a termination character to provide a way to tell how long a string is (i.e. a known stopping point). Else, we only have a starting place and could end up visiting all the memory after it. int w = 0, *x = &w, y = 0; // part a w = 5; y = -1; // part b x = &y; y = 10; w = y + *x; // part c x++; y++; // part d

Front

at A: w = 0 *x = 0 x = add of w y = 0 at B: w = 5 *x = 5 x = add of w y = -1 at C: w = 20 *x = 10 x = add of y y = 10 at D: w = 20 *x = unknown x = add of y + 4 y = 11

Back

Implement LinkedList: Implement list_get that returns the element at the given index of the list void list_get(list lst, int idx) {

Front

int count; node* iter = lst->head; for (count = 0; count <= idx; count++) { if (count != idx) { iter = iter->next; } else { return iter->element; } } return; }

Back

syntactical error

Front

missing or extra character, basically a typo? ex) printf(Hello World); int a = 0

Back

The way a user interacts with an operating system can vary greatly; consider the difference between using a GUI based OS vs. a CLI based one. Give one advantage of using a CLI over a GUI, and one advantage of using a GUI over a CLI. Explain your answers.

Front

A CLI has lower overhead than a GUI and thus can speed up other processes and make using the OS feel faster. Also a CLI is potentially less restrictive because you aren't limited to the options presented to you in a dialog box or menu. A GUI is more user friendly and intuitive to use than a CLI. Also, a GUI can present more information to the user all at the same time, whereas a CLI is more limited in its output.

Back

struct point_2d { int x; int y; Color col; }; void main() { point_2d* data[5]; for(int i = 0; i < 5; i++) { point_2d tmp; tmp.x = i; tmp.y = i * i; data[i] = &tmp; } for(int i = 0; i < 5; i++) printf("Point #%d: %d, %d", i, data[i]->x, data[i]->y); } It prints the same values for x and y for each point_2d struct due to a memory bug. Using heap (dynamic) memory allocation, fix the code above so that it works properly (x and y should be unique for each point). You may add, change, and/or remove lines from the code above for your answer.

Front

void main() { point_2d* data[5]; for(int i = 0; i < 5; i++) { point_2d* tmp = malloc(sizeof(point_2d)); //allocate memory for index tmp->x = i; //assign value tmp->y = i * i; //assign value data[i] = tmp; //remove '&' as tmp is now a pointer } for(int i = 0; i < 5; i++) printf("Point #%d: %d, %d", i, data[i]->x, data[i]->y); free(data[i]); }

Back

char to_upper_case(char original) { char capstr[255]; unsigned int i; for (i = 0; original[i] != '\0'; ++i) { if (original[i] >= 'a' && original[i] <= 'z') { capstr[i] = original[i] - (char)'a' + (char)'A'; } else { capstr[i] = original[i]; } } capstr[i] = '\0'; return capstr; } void main() { printf("%s", to_upper_case("the c programming language")); } a) What will be the output? b) Why does the program give that output? Explain in terms of memory and pointer usage.

Front

a) to_upper_case returns a pointer to a char array which was allocated on the stack. After to_upper_case has finshed, the memory will be released and may be overwritten by subsequent operations. b) Allocate memory for capstr on the heap using malloc, or add a second parameter for the result string and rely on the calling function to allocate memory for the result.

Back

What's the advantage of incrementing a pointer to traverse an array versus using a for-loop? Explain. (Hint: think about 2D arrays.)

Front

When using a pointer to traverse an array, we can simply increment it to move by the byte size of its data type. Thus, with structures such as a multidimensional array, we don't have to worry about exactly indexing the row and column. We simply increment by the byte size to continue to get the next value till we hit null termination. Also, using pointers for incrementing/traversing an array is useful for when we don't know the size of the array (presuming we have a dynamic array), to which we can traverse till we hit null. With a for loop, we have a fixed size of the array, if we don't know the size, then pointer incrementing is more appropriate.

Back

Implement LinkedList: Implement list_indexOf that returns index of the given element or -1 if not found. int list_index(list lst, void ele) { // TODO: IMPLEMENT

Front

int count = 0; node* iter = lst->head; while (iter) { if (iter->element == ele) { return count; } else { iter = iter->next; count++; } } return -1; }

Back

Implement a LinkedList: Write code to bring in the proper header files (LinkedList.h)

Front

#include <stdio.h> #include <stdlib.h> #include "LinkedList.h"

Back

Implement LinkedList: Implement list_is_set. Assigns given element to given index. void list_set(list lst, int idx, void element) { // implement

Front

void list_set(list lst, int idx, void element) { while(lst->size < idx+1) { list_add(lst, NULL); } int cur = idx; node iter = (lst)->head; while(iter != NULL) { if(idx == cur) { iter->element = element; } cur++; } return NULL; }

Back

#define DIST(deltax, deltay) sqrt(deltaxdeltax + deltaydeltay) float x1 = 2.0f, x2 = 5.0f; float y1 = 5.0f, y2 = 10.0f; float dx = x2-x1; float dy = y2-y1; printf("Distance with margin is %f.", DIST(dx++, dy++)); a) What is the output of this program? b) Explain why the output is the way it is. Explain in terms of preprocessing and macro evaluation.

Front

(a) The output is sqrt((34) + (56)) = sqrt(42) (b) After the preprocessor performs macro substitution, DIST(dx++, dy++) becomes sqrt(dx++dx++ + dy++dy++). This means that dx and dy will both be incremented a second time during the multiplication operation, yielding sqrt(34 + 56) = 6.480740.

Back

In general there are two main models for inter-process communication: message-passing and shared memory. Both of these models require different system calls to function because of the difference between how they work. Explain some of the differences between these two approaches at the OS level and what kinds of system calls you would need to use for each model.

Front

Assuming there are multiple processes running that all need to access the same data, a message-passing setup would mean that each process would need to make calls to send() and receive() to share data. A shared memory setup would mean that there's no direct communication between processes; rather each processes makes calls to read(), write(), and reposition()/seek() to access and manipulate the data they need.

Back

What is this code trying to do to the value stored in j, in terms of bits? int* ptr = &j; ((char)ptr) = 0;

Front

It's trying to set the first 8 bits in the binary representation of j to zero.

Back

Consider the problem of padding the following structure, and answer the three questions below. Assume that you are compiling on a system with a 32-bit architecture. struct student { char student_id[10]; char name[75]; float gpa; int credits_earned; int program; }; a) What is the size of this struct as defined? b) How much space would be wasted with word length padding?

Front

a) 12 + 76 + 4 + 4 + 4 = 100 b) 3 bytes (2 in student_id, 1 in name)

Back

Implement LinkedList: Implement list_add. Takes a list and an element, as parameters and append the element to the end of the list. void list_add(list lst, void element) { // TODO: IMPLEMENT

Front

void list_add(list lst, void element) { node new_node = (node)malloc(sizeof(node)); node* iter = lst->head; //set up node new_node->element = element; new_node->next = lst->head; //attach node while(iter->next != NULL) { iter = iter->next; } iter->next = new_node; lst->size++; }

Back

Implement LinkedList: Implement list_destroy (cleans up memory allocations) void list_destroy(list** lst) { //TODO: IMPLEMENT

Front

node iter = (lst)->head; while (iter != NULL) { node* tmp = iter->next; free(iter); iter = tmp; } free(*lst); *lst = NULL; }

Back

void foobar(struct point_2d* p1, int offset) { //...stuff happens... } struct point_2d* build() { struct point_2d data; //...stuff happens... foobar(&data, 10); //LINE 1 return &data; //LINE 2 } Of the two lines indicated, which may be more bug prone? Explain in terms of memory and scope. Assume that the shift function is implemented correctly.

Front

The second. Here we are using a stack allocation. Therefore, on the LINE 1 we know for sure that the memory will be kept alive (the next call is pushed on the stack while this call would stay put). For LINE 2, we are return the address of stack memory which will be returned to the system and used for the next function call.

Back

What is the final code generated after the preprocessor has substituted the macros in the code: #define PI 3.14 #define AREA_OF_SPHERE(r) 4PIr #define VOLUME_OF_CONE(r,h) (1/3)PI(r^2)*h int radius = 3; int height = 5; float area = float volume =

Front

int radius = 3; int height = 5; float area = 43.14radius*radius; float volume = (1/3.0f)3.14radiusradiusheight;

Back

Consider the following pieces of functionality: (a) Saving a word document to a hard drive (b) Applying a blur to a picture foreshadowing (c) Getting the process ID of a different process running on a system Which of these would need to be implemented as a system call? Justify.

Front

(a) Yes. This would need to be implemented using a system call because reading and writing to mass storage is a low level operation that the OS manages. (b) No. The act itself of bluring a picture doesn't require any low-level system calls because such an operation can be done with statically allocated variables and arithmetic. If you were to try to save the blurred image only then would you need a system call. (c) Yes. By default processes don't know about any other processes running on the system. In order to get the process ID of a different processes, you would have to make a system call so the OS could then look up and return the requested ID.

Back