class: center, middle ### COMP26020 Programming Languages and Paradigms Part 1: C Programming *** # The C Standard Library Part 3 ## `strtol` and Stream-based File I/O ??? - Hi everyone, this is a quick video to present you two additional useful features from the C standard library - The `strtol` function used to convert strings into numbers - And stream-based file operations, a series of functions to perform file I/O, that are a bit more high level, in other words easier to use, than the low level functions `open`, `read`, `write`, etc. that we have seen previously --- class: middle, center, inverse # strtol ??? - Let's start with `strtol` --- # The Problem with `atoi` - How to detect malformed strings? ??? - So until now we have been using the function `atoi` to convert a string into a number - The advantage of `atoi` is that it is easy to use, you simply pass the string as parameter and it returns the converted integer - However when the string is malformed or corresponds to a number that is too large or too small to be stored as an integer, `atoi` will not warn you that something has gone wrong -- ```c int main(int argc, char **argv) { if(argc != 2) { printf("usage: %s
\n", argv[0]); return -1; } int n = atoi(argv[1]); printf("n is: %d\n"); return 0; } ``` .codelink[
`13-standard-library-3/atoi-limitation.c`
] ??? - Let's have a look at this example program, that simply uses `atoi` to convert a string from the command line arguments into an integer, and prints that integer -- .center[**`atoi` does not offer any way to know the string is invalid or overflows/underflows an `int`!**] ??? - So as one can see `atoi` does not give you any way to know is the string is invalid or if it will trigger an under or an overflow - Things fail silently and your program continues in an inconsistent state which can be very hard to debug further down the road --- # Solution: Use `strtol` ```c long strtol(const char *nptr, char **endptr, int base); ``` .codelink[[man](https://linux.die.net/man/3/strtol)] ??? - The solution to that problem is to use `strtol` - Its usage is slightly more complicated than `atoi`, but it is also much more robust -- - Convert the string `nptr` into a long integer and return that integer - `base` can be 10, 8, 16, etc. ??? - `strtol` will convert the string pointed by `nptr` into a `long` that is returned - You can specify a base such as 10, 16 for hexadecimal numbers, etc. -- - after the call `*endptr` points to the first invalid character of the string, and to `'\0'` if the string is fully valid ??? - `endptr` is used to check the validity of the string: after the call, what is pointed by `endptr` will point either to the first invalid character of the string, and to `'\0'` if the string is fully valid -- - Under/overflows cause `errno` to be set to `ERANGE`, and the function returns `LONG_MIN` (underflow) or `LONG_MAX` (overflow) ??? - In case of under or overflow, `errno` is set to `ERANGE` and `strtol` returns either `LONG_MIN` if it is an underflow, or `LONG_MAX` if it is an overflow --- # `strtol` Usage Example .leftcol[ ```c /* ... */ #include
#include
int main(int argc, char **argv) { if(argc != 2) { /* ... */ } char *endptr; long n = strtol(argv[1], &endptr, 10); if(*endptr != '\0') { printf("invalid string!\n"); return -1; } if(errno == ERANGE) { if(n == LONG_MIN) printf("underflow!\n"); if(n == LONG_MAX) printf("overflow!\n"); return -1; } printf("n is: %ld\n", n); return 0; } ``` .codelink[
`13-standard-library-3/strtol.c`
] ] ??? - Let's have a look at an example - This program does exactly the same thing as our previous example using `atoi` but this time it uses `strtol` to detect malformed strings and under/overflows - When we call the function in question, we pass as parameter the string, a pointer of pointer of character for endptr, and 10 to indicate a decimal base - Note that because `strtol` wants to return a pointer through the `endptr` parameter, we need to pass a pointer of pointer, a `char **` - It is a pointer to a `char *` that we declared just above -- .rightcol[ - Check string validity by looking at the value of `*endptr` ] ??? - Next we check if the string was valid by observing the value of what is pointed by `endptr` - If it is the `NULL` character the string is valid, otherwise we abort -- .rightcol[ - Check for under/overflows with `errno` and the return value ] ??? - Finally we check that there was no overflow or underflow by looking at `errno` and the return value of `strtol` - If it is the case we abort - If all is well we can print the value of the converted number --- class: middle, center, inverse # Stream-based File I/O ??? - Let's now talk about stream-based file operations --- # Stream-based File I/O ```c FILE *fopen(const char *pathname, const char *mode); ``` .codelink[[man](https://linux.die.net/man/3/fopen)] - Opens the file identified by `pathname` and return a stream object (`FILE *` data structure), returns `NULL` on error ??? - We can create a stream object, also called `FILE *`, with the `fopen` function - It takes the file path as parameter, and returns the stream object or `NULL` if something went wrong -- - `mode` can be: - `"r"`: read-only - `"r+"`: read-write - `"w"`: write-only, truncate file if it exists, create it if it does not - `"w+"`: read-write, truncate file if it exists, create it if it does not - and more, see man page ??? - It also takes another parameter, `mode`, that precises how the file will be accessed: read-only, read-write, as well as what to do if the file does not exists or if it already exists - You can check out the man page for detail about the different modes -- - Close stream with `fclose`: ```c int fclose(FILE *stream); ``` .codelink[[man](https://linux.die.net/man/3/fclose)] ??? - Once you are done with the stream you simply close it with `fclose()` --- # Stream-based File I/O ```c size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); ``` .codelink[[man](https://linux.die.net/man/3/fread)] ??? - Now to read and write in streams representing files, you can use the `fread` and `fwrite` functions -- - Read/write `nmemb` contiguous items, of size `size` each, from/to the file `stream`, to/from the memory pointed by `ptr` ??? - `fread` reads `nmemb` contiguous items, each item being of size `size`, from the file represented by `stream`, into memory at address `ptr` - `fwrite` writes `nmemb` items, each of size `size`, into the file represented by `stream`, from memory at address `ptr` -- - Return the total number of **items** read/written - I.e. total number of bytes transferred only when `size` is 1 ??? - These functions return the number of items read or written, which is only equal to the total number of bytes written when `size` is 1 -- - Example: reading 3 items of size 4 bytes: ```c items_read = fread(buffer, 4, 3, file_ptr); ```
??? - You can see an example illustrated here, in which we use `fread` to read 3 items of 4 bytes each from a file on top, into memory at the bottom - The items are contiguous and we read a total of 3 by 4 equals 12 bytes - Note that similarly to what happens with file descriptors, streams have an internal offset that gets incremented each time we access the file --- # Stream-based File I/O: Example .leftcol[ ```c #include
char *alphabet = "abcdefghijklmnopqrstuvwxyz"; int main(int argc, char **argv) { FILE *f1, *f2; char buffer[27]; f1 = fopen("test-file.txt", "w"); if(f1 == NULL) { perror("fopen"); return -1; } if(fwrite(alphabet, 2, 13, f1) != 13) { perror("fwrite"); fclose(f1); return -1; } fclose(f1); ``` ] .rightcol[ ```c f2 = fopen("test-file.txt", "r"); if(f2 == NULL) { perror("fopen"); return -1; } if(fread(buffer, 1, 26, f2) != 26) { perror("fread"); fclose(f2); return -1; } buffer[26] = '\0'; printf("read: %s\n", buffer); fclose(f2); return 0; } // ``` .codelink[
`13-standard-library-3/file-stream.c`
] ] ??? - Now let's see a complete program using stream-based file I/O - We start by opening a first stream in write only mode - This mode will create the file if it does not exist, and if it does it will truncate its size to 0 - Next we want to write the entirety of the string alphabet in the file - Using `fwrite`, we can do it for example as 13 chunks of size 2 bytes each - Once done we close the stream - And we open a new one, in read mode this time - Note that because this is a new stream independent from the previous one, its offset will be set to 0 in the file - Now we want to read what we previously wrote in the file - With `fread` we can do it for example as 26 chunks of 1 byte each - We fix up the buffer storing the result with the termination character to make it a proper C string - And we can display its content --- # Summary - `strtol` to convert strings to integers in a robust way - `FILE *` I/O, higher level operations than `read`/`write`/etc. *** .center[Feedback form: https://bit.ly/3R8dSGr]
??? - And that's it! - We saw `strtol`, a much more robust way to convert strings into numbers compared to `atoi` - We also saw stream-based file I/O, a higher level method to perform file operations compared to `open`, `read`, `write` and so on.