Week 11 Day 1 - From Low Level Syscalls to High Level Modern C++
Today we talked about some of the oldest and lowest level stuff you'll need to deal with, and also some of the newest stuff (so new, it's not on the Raspberry Pi we use as a server, and had to switch to the normal Intel-based server).
We learned:
1) System calls when doing I/O always use something called a "file descriptor" which is a fancy term for an integer.
2) When you use the open() syscall to open a file, it returns an int (which we call a file descriptor). From then on, you use that file descriptor any time you want to read or write to that file.
3) You can also open pipes and sockets to read and write from. They're also just file descriptors (fd for short), i.e. just ints.
4) You can use the read() and write() system call to read and write from file descriptors. You pass them a memory address (usually the starting address of an array of some type) and a maximum number of bytes to read and write. That's it.
5) You can also use close() with dup() (or just dup2()) to close a file descriptor, say, for the keyboard input, and then set up a file to use file descriptor 0 (stdin). Then your program will think it is reading from the keyboard, but is actually reading from a file.
6) You can do this with file descriptor 1 (stdout) and 2 (stderr) as well, this is how your shell creates pipes between different programs. The programs think they're reading and writing to stdin and stdout, but the shell has duped them into actually reading and writing to other programs.
7) To actually make a new process, you need to use fork(). Fork() is an important system call you can use to duplicate a process running. A simple use of this is if you want to spawn off multiple copies of your process to run on multicore systems. You can then use wait() or waitpid() for a parent process to wait for a child process to finish. You know if you're a parent or child process by the return value from fork().
8) Exec() is another crucial system call. It replaces the currently running process with another process (for example "/usr/bin/vim") and can optionally pass it some command line arguments (like "main.cc"). It then stops running the current process and runs the new process instead. This is the loader step in the CALL acronym we talked about last time.
I walked you all through the entire process of how it works from when you log in to being able to run Vim on a file.
Flipping it back around to high level C++ stuff, we talked about better C++ alternatives to the C versions I tend to teach in CSCI 40, since they can be taught and memorized quickly. However, y'all are advanced enough now to see the technically superior (in every way except verbosity) way of doing these things in modern C++.
1) We talked about the chrono library in modern C++, and how it is superior to clock() and time(). There's steady_clock (which we typically use for measuring durations since it can't run backwards) or high_resolution_clock which on some systems might have more precision. I gave sample code showing how to use it. It's verbose (and can be a LOT more verbose), but it's nice. I use it.
2) We then talked about the more proper way of doing random numbers than using rand(). Modern C++'s random number generation system is much nicer than rand() from a technical standpoint and garbage from a usability standpoint. First semester students will never be able to memorize three or four lines of arbitrary lines of code (with symbols like mt19937 in them) without copying and pasting from cppreference, but they can all call rand().
3) I also showed a small touch of functional programming here by binding (using std::bind) a function operator and a parameter to it to make a simple to use function that would generate numbers from 1 to 10 or whatever.
4) We then talked about using generate() and iota() to initialize arrays with random data or monotically increasing data, respectively, and why you might want to use them instead of a small for loop to do the same thing. Basically, it expresses your intent better to other programmers and the compiler, and when we get parallel execution going, it might just turn out to be faster when you wake up some day.
5) Finally, we talked about ranges, which FINALLY did something I've been wanting to see in C++ for a long time, the elimination of needing .begin() and .end() when you want to work over an entire container (which is most of the time). So you can just ranges::sort(vec) now, rather than std::sort(vec.begin(),vec.end());
Yay!