Well, I think I'm finally at a good point with my linked list class. I may add a few more functions that, perhaps, compare linked lists to see if they're the same or copy one linked list into another linked list. As it stands right now many of the functions are overloaded to handle multiple data types. I can append and insert nodes into the list as well as delete nodes. Creating this class and implementing it really boosted my knowledge on doubly linked lists. At first, I was like, “huh?” but now I feel I've reached a decent level of understanding. Doing the insert functions was where I really had to think about things. Dropping a new node and relinking all the pointers to the right places really hurts the brain at first. I'd also like to try an implementation with templates. This will minimize the number of functions and, hopefully, will really cement my understanding of the subject.
I think I've come to understand the whole function pointer thing. It was just hard to come up with a practical use for them. In, C++ I remember discussing that the only difference between a class and a struct in C++ was that a class contains private members whereas in a function everything is public. However, in C structs cannot contain any functions. However, it is possible to create a function and put a pointer function within the struct. Now you can have a semi-sort of member function for your struct.
We've moved onto creating a stack today. Well, I guess I should say yesterday at this point but… At first it was kind of weird. I thought it would be difficult to mold the idea into my Linked List Class but it really didn't take that much. Instead of creating several Node pointers, I just instantiated a linked list object called stack. To push to the stack, I would just call the insert function and insert to the first point in the list. When popping from the stack I would just get the value at the first point in the list and then delete that node. Right now, I'm trying to get a palindrome checker using the stack but it's being a little goofy.
This is a sample format for a dated entry. Please substitute the actual date for “Month Day, Year”, and duplicate the level 4 heading to make additional entries.
As an aid, feel free to use the following questions to help you generate content for your entries:
Remember that 4 is just the minimum number of entries. Feel free to have more.
A pointer is a data type that contains the memory address where another value is stored in memory.
#include <stdio.h> int main() { int regVar = 0; int *pointVar = ®Var; printf("regVar is stored at 0x%x. \n", ®Var); printf("pointVar points to 0x%x. \n", pointVar); printf("regVar = %d\n", regVar); printf("The value of pointVar = %d\n", *pointVar); return 0; }
lab46:~$ ./pointer regVar is stored at 0x22ff74. pointVar points to 0x22ff74. regVar = 0 The value of pointVar = 0 lab46:~$
An array is a sequence of equally spaced address in memory. Arrays usually come in two flavors: one dimensional arrays and two dimensional arrays. One dimensional arrays are in a row format. Ex. array[0], array[1], array[3]… Two dimensional arrays use a row column format. Ex. array[0][0], array[0][1], array[0][2]…
Pointer arithmetic is a means of manipulating a position in memory via addition or subtraction. Pointer arithmetic and array subscripting are two ways of doing the same thing. To do the same
#include <cstdlib> #include <iostream> using namespace std; int main() { int *intPointer, x; intPointer = (int*) malloc(sizeof(int)*5); for (x = 0; x < 5; x++) cout << intPointer++ << endl; return 0; }
lab46:~$ ./pointerArithmetic 0x2145010 0x2145014 0x2145018 0x214501c 0x2145020 lab46:~$
In C/C++, a null pointer is a special case. Null is a mnemonic which makes it a little clearer that there is no object associated with a pointer. Integers and memory addresses are interchangeable. Zero, null, is the exception because in C it is guaranteed that 0 will never be a valid memory location. Attempting to access a null pointer would be bad. If you're fuzzy on the whole good bad thing, I want you to imagine all life as you know it stopping instantaneously, and every molecule in your body exploding at the speed of light.
#include <stdio.h> int main() { char *cp = NULL; printf("%c\n", *cp); return 0; }
lab46:~/src$ ./nullPoint Segmentation fault (code dumped) lab46:~/src$
A void pointer can be looked at kind of like a shape shifter. It can store an address to any data type. In C, it will be converted to any other data type when assigned. However, it can't be dereferenced without a cast.
#include <stdio.h> int main() { int i = 0; char c = 'A'; void* q = &i; printf("%d\n", *(int*)q); q = &c; printf("%c\n", *(char*)q); return 0; }
lab46:~$ ./voidPoint 0 A lab46:~$
Static memory allocation is the process of the allocating memory at compile time. This happens when variables are declared locally within a function.
{ int i, j; i = 1; j = i + 4; }
With dynamic memory allocation, memory is created at run time. In C, malloc() is used to allocate memory and in C++ the keyword new is used. This memory will exist until the user deallocates it via free() in C, and delete in C++ or the garbage collector takes care of it. In C
{ int *i = NULL; i = malloc(sizeof(int)); free(i); return 0; }
In C++
{ int *i; i = new int; delete i; return 0; }
A structure, or struct, is a compound data type consisting of multiple variables of, possibly, different data types. This makes for a nice way to group information that is related. This can be particularly useful when accessing records within a file. For instance, you may have a file which contains the first and last name and age of the employees at a company.
struct empInfo { char firstName[50]; char lastName[50]; int age; };
If we want to access the members of a struct, we'd do as follows.
struct empInfo joe; joe.firstName = "Joe"; joe.lastName = "Smith"; joe.age = 29;
A structure pointer is a much like any other pointer one would use. Using the structure example above, to create a pointer to that structure, one would do as follows:
struct empInfo *empPoint; empPoint = malloc(sizeof(struct empInfo));
Accessing the members of a struct pointer is a little different than accessing the members of a struct declared statically. Lets say we want to store the age of an employee into our struct, we'd do the following:
empPoint -> age = 20;
A linked list is an unspecified number of structure pointers all chained together. Only the start and the end are specified. Every other part of the chain is linked by the “next” pointer in the previous part of the chain. In order to maneuver through the list, one needs to resume back to the beginning of the list and move forward.
struct Node { int value; struct Node *next; };
The above struct is the core of a linked list. The variable value stores what integer data we have. The node pointer next points to the next item in the list. If there is only one item in the list or this node is the end of the list, next points to NULL. If we're somewhere in the middle of the list, next will point to whatever the next item in the list is.
int value, i; if (pos > listLength || pos < 0) pos = 0; tmp = start; for (i = 0; i < pos; i++) tmp = tmp -> next; value = tmp -> intVal;
This code first checks if the desired position in the list is out of bounds and sets the pos to 0. We then move to the beginning of the list and cycle through until we reach the desired position and retrieve the value
A doubly linked list isn't much different that a singly linked list. The only major difference is that your struct will also contain a pointer to the previous item in the list. With this type of list you can move from either the beginning of the list to the end or from the end to the beginning.
struct Node { int value; struct Node* prev; struct Node* next; };
The only difference between this struct and the one from above is the Node pointer prev. This means that there will be a few more loose ends to tie up. So, if we were to delete a node somewhere in the middle of the list.
tmp -> next -> prev = tmp -> prev; tmp -> prev -> next = tmp -> next; tmp -> prev = NULL; tmp -> next = NULL; free(tmp);
In the above code, first, we set the prev pointer in the next list element to point at the list element before tmp. So, if we have 3 nodes One, Two, and Three, and we're deleting node Two, we're Three's prev to point at Two. Now we set the next pointer of the previous element in the list to point at the node after tmp. So, One's next will point at Three. We then set all of the pointers within Two to NULL and deallocate the memory.
A function pointer is a bit of a special case pointer. A function starts at a certain address in memory. We can dereference a pointer to invoke a function.
#include <stdio.h> int add(int, int); int sub(int, int); struct Thing { int val1, val2; int (*addition)(int, int); int (*subtraction)(int, int); }; int main() { int x, y; struct Thing myThing; myThing.addition = &add; myThing.subtraction = ⊂ printf("First int: "); scanf("%d", &x); printf("Second int: "); scanf("%d", &y); printf("Addition: %d\n", myThing.addition(x, y)); printf("Subtraction: %d\n", myThing.subtraction(x, y)); return 0; } int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; }
In C, it's not possible to declare a function inside of a struct. However, we can create function pointers to work around and have some functions within our struct to process the struct's data.
Version control, in relation to our coursework, is the monitoring and manipulation of the changes within our source code. We can create changes and upload them to our repository, revert to an older change within the repository, and also we can view any changes that have been made.
lab46:~/src/cpu$ svn update At revision 432. lab46:~/src/cpu$
lab46:~/src/cpu$ ------------------------------------------------------------------------ r3 | bh011695 | 2011-01-27 13:05:23 -0500 (Thu, 27 Jan 2011) | 1 line added nandGate function ------------------------------------------------------------------------ r2 | jr018429 | 2011-01-27 09:56:47 -0500 (Thu, 27 Jan 2011) | 1 line added boolean operators ------------------------------------------------------------------------ r1 | wedge | 2011-01-23 19:30:06 -0500 (Sun, 23 Jan 2011) | 1 line Initial commit ------------------------------------------------------------------------
In Unix memory is segregated into two sections. First there is kernel space and secondly there is also user space. User space encompasses the area where the memory is used for user applications.
The location in memory where the operating system is stored is referred to as system space or kernel space. The operating system is referred to as the kernel. The kernel has sole access to any kind of peripheral device such as a printer, mouse, or keyboard. If an application wants to access any of these devices, it requires permission from the kernel.
The function open of type int which returns a file descriptor. If opening the file was unsuccessful a -1 will be returned, otherwise will return an integer. It takes, as parameters a filename and an int constant, the read/write flag, to tell the function what you plan on doing with the file: reading, writing, or reading and writing.
read() is a system call which reads from a file descriptor. As parameters, it takes a file descriptor, a void pointer buf, and a count. read() will read count number of bytes starting from buf into the buffer from the file descriptor.
write() is used to store data into a file. write() has the parameters: a file descriptor, buffer, and amount. write() will take amount bites from buffer and send them to the file descriptor. If successful, write() will return the number of bytes written. If it fails it will return a -1. It is possible for the number of bytes sent to be different from the number of bytes requested. The system could possibly put a limit on the size of a file. There is also the possibility of disk space. Perhaps there is no more space left on the hard drive.
lseek() is used to find an offset within a file. It takes, as parameters, a file descriptor, the distance in bytes from the base, and the base which can be 0: start of file, 1: current position within the file, or 2: the end of the file.
Whenever finished with a file descriptor, it needs to be closed. close() takes as a parameter a file descriptor and returns a 0 upon success and a -1 on error. An error, for example, if you were to try to open a file that does not exist a -1 will be returned by close().
#include <stdio.h> #include <stdlib.h> #define OFFSET 100 #define BASE 0 #define BUFF_SIZE 50 int main(int argc, char** argv) { char *filePath, c[BUFF_SIZE]; int fd1, fd2; filePath = (char*) malloc(sizeof(char) * 100); if (argc != 2) { printf("error: no filepath entered\n"); } else { fd1 = open(argv[1], "r"); fd2 = creat("myFile", 644); lseek(fd1, OFFSET, BASE); read(fd1, c, BUFF_SIZE); write(fd2, c, BUFF_SIZE); close(fd1); close(fd2); } return 0; }
The data outputted to the file is a tad unintelligible. I thought I was doing everything right. It would appear that things aren't getting read into the buffer properly but I'm not sure why that would be.
A file descriptor is a means for which a process can talk to a file. When you open a file, a file descriptor will be created. A -1 signifies that there was an error. However, if there was no error an integer will be returned that will. This is the file descriptor. Any files opened will have a different file descriptor. This file descriptor will be used for operations performed on the file.
fd = open("myFile", "r");
This is how one would create a file descriptor.
Buffering is the means of taking data and storing in memory in chunks of a predetermined size. An example of this would be prompting the user for some character data and storing the first 50 bytes into an array. Once the desired operations have been performed on the data another 50 bytes would be stored into the array. Repeat this until there is no more data to process. The kernel has its own buffers which it uses to save time. It keeps copies of disk blocks in memory because of how long it would take to keep writing back and forth between memory and the hard disk.
A system call is a function that requests a service from the kernel. System calls can be divided into several groups: process control, file management, device management, information maintenance and communication. read() write() open() and close are all examples of a system call.
When you do an ls -l a lot of information is displayed. However, on the far left you can find the file type designation. There are many different types of files such as directories, regular files, sockets, symbolic links, or named pipes. In one of the earlier examples we used creat() to create a file. When creat() is called a filetype is designated to to the file. It is simply just a regular file. However, different system calls can be used to create different types of files.
A file also has many different properties such as its owner, its permissions, its name, the last time it had been written to, its size, or its create data. Like the file type, all of these things can be displayed by doing a simple ls -l. Using chown(), it is possible to be able to change the owner of a file. As parameters, chown() takes the path to the file, the owner id, and the group id. It is also possible to change the permission bits of a file using chmod(). It's parameters are a file path and the new permissions.
bh011695@lab46:~/discrete$ ls -l total 776 -rw-r--r-- 1 bh011695 lab46 96 Sep 17 13:05 Makefile -rwxr-xr-x 1 bh011695 lab46 684685 Sep 23 23:35 a.out -rwxr-xr-x 1 bh011695 lab46 7104 Sep 7 15:45 assignmentFive -rw-r--r-- 1 bh011695 lab46 654 Sep 9 17:34 assignmentFive.c -rwxr-xr-x 1 bh011695 lab46 7065 Sep 10 10:07 assignmentOne -rw-r--r-- 1 bh011695 lab46 602 Sep 9 17:36 assignmentOne.c -rwxr-xr-x 1 bh011695 lab46 8553 Sep 9 15:38 assignmentTwo -rw-r--r-- 1 bh011695 lab46 776 Sep 9 17:33 assignmentTwo.c -rwxr-xr-x 1 bh011695 lab46 8046 Sep 17 13:09 count -rw-r--r-- 1 bh011695 lab46 344 Sep 17 13:09 count.c -rw-r--r-- 1 bh011695 lab46 1872 Sep 17 13:09 count.o -rw-r--r-- 1 bh011695 lab46 4154 Sep 23 23:34 libStringLib.a drwxr-xr-x 2 bh011695 lab46 4096 Sep 29 10:21 stringLib -rw-r--r-- 1 bh011695 lab46 874 Sep 17 12:30 stringLib.c -rw-r--r-- 1 bh011695 lab46 228 Sep 17 12:30 stringLib.h -rw-r--r-- 1 bh011695 lab46 2312 Sep 23 23:39 stringLib.o -rwxr-xr-x 1 bh011695 lab46 7697 Sep 23 23:40 test -rw-r--r-- 1 bh011695 lab46 3237 Sep 24 14:31 test.c -rw-r--r-- 1 bh011695 lab46 1552 Sep 23 23:39 test.o -rw-r--r-- 1 bh011695 lab46 120 Sep 23 23:30 test1.c
Bit masking involves checking to see if a certain bit is a one or a zero. To turn a bit on a logical or operation is used. To turn a bit off a logical and is used. It also possible to check the status of a single bit or to change multiple bit values.
#include <stdio.h> int main() { int num = 97; int mask = 128; printf("num = %d\n", num); //Turn on the left most bit printf("num = %d\n", num = num | mask); //turn off the left most bit mask = 127; printf("num = %d\n", num = num & mask); return 0; }
lab46:~$./mask num = 97 num = 225 num = 97
An unsigned integer value is designated to every user within the system. This UID ranges between 0 and 32767. 0 is always designated to root. A GID, or group ID, follows the same convention. Any user on the system can run the id command to obtain their user and group ID.
bh011695@lab46:~$ id uid=5577(bh011695) gid=5000(lab46) groups=5000(lab46),1734(sys),1738(data),2999(bigworld),5577(bh011695)
An inode stores information about a file The only things the inode does not contain are the files data and the file's name. Every file on the system has an inode associated with it. All of the inodes on the system are contained inside of an inode table. Data blocks are where the file data is actually stored. When a file is to be stored, the kernel will locate however many blocks are needed to store the file. These data blocks may or may not be adjacent to each other. The kernel will then store the information within these data blocks.
In Unix, a directory is a special type of file. A directory contains all of the names of the files stored within it. To be able to view the files of a directory with their respective inode numbers is to do an ls -1ia.
bh011695@lab46:~$ ls systemsProg -1ia 573673308 . 66461891 .. 573676539 .swp 573654795 Output.txt 573673286 cli 573673302 cli.c 573655652 filesandargs.c 573655709 fork.c 573655682 fork2.c 573654788 fprintf 573655613 fprintf.c 573655696 fscanf.c 573655614 getopt.c 573655620 mtest 573655617 mtest.c 573655706 output.txt 573655618 screentest.c 573655707 stringmanip.c
Looking at the output, there is a big list of numbers followed by file names. For instance inode 573655707 is associated with stringmanip.c. All of the meta data for stringmanip.c is stored within this inode.
Symbolic links or just links for short are very much like a shortcut in windows. A link is a file that points to another file. The reason for this is because multiple files can have the same inode number.
bh011695@lab46:~/ASM_6502$ ln helloWorld.asm newHi.asm bh011695@lab46:~/ASM_6502$ ls -1ia 553450825 . 66461891 .. 556055369 0x8.65s 556055370 0x9.65s 556055378 0xA.65s 553450824 ASM_EOCE.txt 556055371 helloWorld.asm 556055371 newHi.asm
If we look at the output of ls after doing the ln, we see that helloWorld.asm and newHi.asm both share the same inode number.
Choose the appropriate data structure for solving a given problem. Apply the data structure that would be best suited for solving some problem.
I've used our stack implementation from earlier to be able to solve the palindrome problem.
#include "LinkedList.h" #include <cstdio> #include <cstdlib> void push(char, LinkedList&); char pop(LinkedList&); int main() { LinkedList stack; char *string, *start, c; int unequal = 0; string = (char*) malloc(sizeof(char)*50); start = string; printf("Enter a string: "); scanf("%s", string); printf("Your string is %s.\n", string); while (*string != '\0') push(*string++, stack); string = start; while (stack.length() > 0 ) { if (pop(stack) != *string++) unequal = -1; stack.del(0); } string = start; if (unequal == 0) printf("%s is a palindrome.\n", string); else printf("%s is not a palindrome.\n", string); return 0; } void push(char value, LinkedList &s) { s.insert(value, 0); } char pop(LinkedList &s) { return s.getCharVal(0); }
lab46:~$ ./palindrome Enter a string: noon Your string is noon. Noon is a palindrome ./palindrome Enter a string: lair Your string is lair. lair is not a palindrome.
Reflect upon your results of the measurement to ascertain your achievement of the particular course objective.
Better understand file I/O for efficient data processing. Be able to read and write using C functions.
I've created a bit of a file copying program. It opens an input file, reads the character data into it, and then copies it into a new file.
#include <stdio.h> #include <stdlib.h> int main(int argc, char** argv) { char *filePath; int inChar; FILE *inFile, *outFile; filePath = (char*) malloc(sizeof(char) * 100); if (argc != 2) { printf("Path to file: "); scanf("%s", filePath); inFile = fopen(filePath, "r"); outFile = fopen("fileCopy.txt", "w"); } else { inFile = fopen(argv[1], "r"); outFile = fopen("fileCopy.txt", "w"); } while ((inChar = fgetc(inFile)) != EOF) fputc(inChar, outFile); fclose(inFile); fclose(outFile); return 0; }
It does what it's supposed to. It reads input from a file and then outputs it into a new file.
Reflect upon your results of the measurement to ascertain your achievement of the particular course objective.
I had some issues with part of the project in discrete. I was having a little confusion as passing a pointer. I'm used to either passing a normal variable by value or passing by referencing using &. So, the question is, aren't sending a pointer and passing by reference the same thing?
Well, from what I know about pointers, when you pass one to a function you're passing a memory address. When you pass by reference, you pass the memory address of the variable that you're passing, a pointer. http://www.cplusplus.com/forum/beginner/7117/
Passing a variable by reference and passing a pointer to a variable will achieve the same result.
How are you going to test your hypothesis? What is the structure of your experiment?
#include <cstdio> void first(int&); void second(int*); int main() { int i = 25; int *j = &i; first(i); second(j); return 0; } void first(int& i) { printf("%d\n", i); } void second(int *i) { printf("%d\n", *i); }
lab46:~$ ./exp1 25 25
Based on the data collected:
For the most part, sending a pointer to a function and passing a variable by reference isn't much difference. The only real difference between the two of these is the ability to set a pointer to NULL which doesn't exist when passing by reference.
Something I've wondered in C++ there is a string type however, in C we create a pointer or an array of characters. Are these two things identical in size?
The two should be the same size. A C string should be one byte times the number of elements and the string type, unless there's added overhead, should be the same size. However, I want to say that I've tested this before and the two variables were of different sizes. Thought I can't quite remember.
A C string and a C++ string type variable should be the same size in bytes if they are of the same length.
How are you going to test your hypothesis? What is the structure of your experiment?
int main() { char *s1 = "cats"; string s2 = "cats"; printf("s1 is %d bytes\n", sizeof(s1)); printf("s2 is %d bytes\n", sizeof(s2)); return 0; }
lab46:~$ ./size s1 is 4 bytes s2 is 4 bytes
Based on the data collected:
It does turn out that both the C String and the string type are the same size if they are of the same length. However, I'm NOT sure as to why they're only four bytes. “cats\0” should be 5 bytes I would think.
The SysProg book had an experiment. Create a shell script that seems to recursively create directories. Even a second after there should be lots and lots of directories. Kind of like sticking a mirror in front of another mirror and looking at it. The question is, how do you delete them?
The book says that it shouldn't take long to create a directory tree that will go beyond the abilities of most commands that work on directory trees.
There won't be a simple command that deletes this tree. Rm will not delete a directory if it contains any other files. Rmdir works very much the same and lacks a recursive option. Neither of these should work.
The experiment will be to use a shell script to create a gigantic directory tree. After the tree has been created, check the number of sub-directories with ls. Confirm this by cd-ing through the tree. Lastly, try to delete the tree with a fairly simple command.
Create a huge file tree
#!/bin/bash while true do mkdir deep-well cd deep-well done
Doing an ls -lh gives us the following output on our new directory tree
drwxr-xr-x 3 ubuntu ubuntu 2.0k 2011-10-01 12:57 deep-well
I strolled through the tree and found the directory count, 3, in the ls was quite wrong. So, if that doesn't work, will it be possible to delete the tree using standard rm and rmdir. Both of these result in failure.
ubuntu:~$ rm deep-well/ rm:cannot remove `deep-well/': Is a directory ubuntu:~$ rmdir deep-well ubuntu:~$ rmdir: failed to remove `deep-well': Directory not empty
Now what happens when we add the force and recursive options to rmdir?
ubuntu@ubuntu:~/scripts$ ls 2Fake.sh deepWell.sh guessing_game.sh random_num_generator.sh anagram.sh dialog.sh jumble2.sh test_jumble.sh array.sh dice_game.sh jumble.sh yrProd.sh auto_draw fake_work.sh mousescroll.sh deep-well ghostarray.sh palindrome.sh ubuntu@ubuntu:~/scripts$ cd deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/ ubuntu@ubuntu:~/scripts/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well$ pwd /home/ubuntu/scripts/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well ubuntu@ubuntu:~/scripts/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well$ cd ~/scripts ubuntu@ubuntu:~/scripts$ rm -rf deep-well/ ubuntu@ubuntu:~/scripts$ ls 2Fake.sh deepWell.sh ghostarray.sh mousescroll.sh yrProd.sh anagram.sh dialog.sh guessing_game.sh palindrome.sh array.sh dice_game.sh jumble2.sh random_num_generator.sh auto_draw fake_work.sh jumble.sh test_jumble.sh ubuntu@ubuntu:~/scripts$
Based on the data collected:
It is possible to delete a fairly large directory tree with a simple command line. However, it would be worth retesting while keeping track of how long the shell script runs for, 5 seconds, 10 seconds, etc. It is possible that it's simply not running long enough. There may be a point where commands will no longer be able to do any kind of operations on the tree.