Images in this article are not well-scaled and some of them may not be accessed.
Disclaimer: This passage contains notes and insights gathered from the internet. The content is used for educational purposes and attributed to the original authors. I do not claim any ownership over the material.
System Arguments
Memory Structure
stack的memory地址是最大的
Pointer
Syntax
/* one pointer, one regular int */ int *pointer1, nonpointer1; /* two pointers */ int *pointer1, *pointer2;
if you declare multiple pointers on the same line, you must precede each of them with an asterisk.
Graphs
Dynamic Memory Allocation
malloc & free
// Determine the size of an integer and allocate that much heap space. malloc(sizeof(int));
The malloc function returns a void * type, which represents a generic pointer to a non-specified type (or to any type).
p = (int *) malloc(sizeof(int)); free(p); p = NULL;
Syntax
typedefstruct { char * name; int age; } person;
person * myperson = (person *) malloc(sizeof(person));
The reason we write person * before the call to malloc() is that malloc() returns a void pointer, which is a pointer that doesn’t have a type. Writing person * in front of it is called typecasting, and changes the type of the pointer returned from malloc() to be person.
Note that sizeof is not an actual function, because the compiler interprets it and translates it to the actual memory size of the person struct.
// This step will evaluate the size of whatever we would get back from dereferencing ptr int *ptr = malloc(sizeof(*ptr));
float *ptr; /* hundreds of lines of code */ ptr = malloc( sizeof(*ptr) );
When 0 is assigned to a pointer, the pointer becomes a null pointer, in other words, it points to nothing.
free(ptr); ptr = NULL;
Allocated Arrays & Strings
int *arr; char *c_arr;
// allocate an array of 20 ints on the heap: arr = malloc(sizeof(int) * 20);
// allocate an array of 10 chars on the heap: c_arr = malloc(sizeof(char) * 10);
eg
/* these two statements are identical: both put 8 in index 0 */ d_array[0] = 8; // put 8 in index 0 of the d_array *d_array = 8; // in the location pointed to by d_array store 8
Make a single call to malloc, allocating one large chunk of heap space to store all NxM array elements.
Memory Efficient. All elements will be allocated at once, in contiguous memory locations
#define N 3 #define M 4
intmain() { int *two_d_array; // the type is a pointer to an int (the element type)
// allocate in a single malloc of N x M int-sized elements: two_d_array = malloc(sizeof(int) * N * M);
if (two_d_array == NULL) { printf("ERROR: malloc failed!\n"); exit(1); }
...
voidinit2D(int *arr, int rows, int cols) { int i, j; for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { arr[i*cols + j] = 0; } } }
Make multiple calls to malloc, allocating an array of arrays. First, allocate a 1D array of N pointers to the element type, with a 1D array of pointers for each row in the 2D array. Then, allocate N 1D arrays of size M to store the set of column values for each row in the 2D array. Assign the addresses of each of these N arrays to the elements of the first array of N pointers.
More programmer-friendly
// the 2D array variable is declared to be `int **` (a pointer to an int *) // a dynamically allocated array of dynamically allocated int arrays // (a pointer to pointers to ints) int **two_d_array; int i;
// allocate an array of N pointers to ints // malloc returns the address of this array (a pointer to (int *)'s) two_d_array = malloc(sizeof(int *) * N);
// for each row, malloc space for its column elements and add it to // the array of arrays for (i = 0; i < N; i++) { // malloc space for row i's M column elements two_d_array[i] = malloc(sizeof(int) * M); }
voidinit2D_Method2(int **arr, int rows, int cols) { int i,j;
for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { arr[i][j] = 0; } } }
Strings and String Lib
// strcpy copies the bytes from the source parameter (str1) to the // destination parameter (str2) and null terminates the copy. strcpy(str2, str1);
new_str = malloc(sizeof(char) * (size+1)); // need space for '\0' if(new_str == NULL) { printf("Error: malloc failed! exiting.\n"); exit(1); } strcpy(new_str, str); printf("%s %s\n", str, new_str); // prints "Hello Hello"
strcat(str, " There"); // concatenate " There" to the end of str printf("%s\n", str); // prints "Hello There"
free(new_str); // free malloc'ed space when done new_str = NULL;
return0; }
The string library (string.h) is particularly useful when writing programs that use C strings. The stdlib.h and stdio.h libraries also contain functions for string manipulation, and the ctype.h library contains functions for manipulating individual character values.
strlen & strcpy & strncpy
// returns the number of characters in the string (not including the null character) intstrlen(char *s);
// copies string src to string dst up until the first '\0' character in src // (the caller needs to make sure src is initialized correctly and // dst has enough space to store a copy of the src string) // returns the address of the dst string char *strcpy(char *dst, char *src);
// like strcpy but copies up to the first '\0' or size characters // (this provides some safety to not copy beyond the bounds of the dst // array if the src string is not well formed or is longer than the // space available in the dst array); size_t is an unsigned integer type char *strncpy(char *dst, char *src, size_t size);
#include<stdio.h> #include<stdlib.h> #include<string.h>// include the string library
intmain() { // variable declarations that will be used in examples int len, i, ret; char str[32]; char *d_str, *ptr;
strcpy(str, "Hello There"); len = strlen(str); // len is 11
strncpy(d_str, str, 5); d_str[5] = '\0'; // explicitly add null terminating character to end
printf("%d:%s\n", strlen(str), str); // prints 11:Hello There printf("%d:%s\n", strlen(d_str), d_str); // prints 5:Hello
free(d_str);
return0; }
strcmp & strncmp
intstrcmp(char *s1, char *s2); // returns 0 if s1 and s2 are the same strings // a value < 0 if s1 is less than s2 // a value > 0 if s1 is greater than s2
intstrncmp(char *s1, char *s2, size_t n); // compare s1 and s2 up to at most n characters
strcpy(str, "alligator"); strcpy(d_str, "Zebra");
ret = strcmp(str,d_str); if (ret == 0) { printf("%s is equal to %s\n", str, d_str); } elseif (ret < 0) { printf("%s is less than %s\n", str, d_str); } else { printf("%s is greater than %s\n", str, d_str); // true for these strings }
ret = strncmp(str, "all", 3); // returns 0: they are equal up to first 3 chars
strcat & strstr & strchr
// append chars from src to end of dst // returns ptr to dst and adds '\0' to end char *strcat(char *dst, char *src)
// append the first chars from src to end of dst, up to a maximum of size // returns ptr to dst and adds '\0' to end char *strncat(char *dst, char *src, size_t size);
// locate a substring inside a string // (const means that the function doesn't modify string) // returns a pointer to the beginning of substr in string // returns NULL if substr not in string char *strstr(constchar *string, char *substr);
// locate a character (c) in the passed string (s) // (const means that the function doesn't modify s) // returns a pointer to the first occurrence of the char c in string // or NULL if c is not in the string char *strchr(constchar *s, int c);
char str[32]; char *ptr;
strcpy(str, "Zebra fish"); strcat(str, " stripes"); // str gets "Zebra fish stripes" printf("%s\n", str); // prints: Zebra fish stripes
strncat(str, " are black.", 8); printf("%s\n", str); // prints: Zebra fish stripes are bla (spaces count)
ptr = strstr(str, "trip"); if (ptr != NULL) { printf("%s\n", ptr); // prints: tripes are bla }
ptr = strchr(str, 'e'); if (ptr != NULL) { printf("%s\n", ptr); // prints: ebra fish stripes are bla }
strtok & strtok_r
A token refers to a subsequence of characters in a string separated by any number of delimiter characters of the programmer’s choosing.
char *strtok(char *str, constchar *delim);
// a reentrant version of strtok (reentrant is defined in later chapters): char *strtok_r(char *str, constchar *delim, char **saveptr);
/* * Extract whitespace-delimited tokens from a line of input * and print them one per line. * * to compile: * gcc -g -Wall strtokexample.c * * example run: * Enter a line of text: aaaaa bbbbbbbbb cccccc * * The input line is: * aaaaa bbbbbbbbb cccccc * Next token is aaaaa * Next token is bbbbbbbbb * Next token is cccccc */
intmain() { /* whitespace stores the delim string passed to strtok. The delim * string is initialized to the set of characters that delimit tokens * We initialize the delim string to the following set of chars: * ' ': space '\t': tab '\f': form feed '\r': carriage return * '\v': vertical tab '\n': new line * (run "man ascii" to list all ASCII characters) * * This line shows one way to statically initialize a string variable * (using this method the string contents are constant, meaning that they * cannot be modified, which is fine for the way we are using the * whitespace string in this program). */ char *whitespace = " \t\f\r\v\n"; /* Note the space char at beginning */
char *token; /* The next token in the line. */ char *line; /* The line of text read in that we will tokenize. */
/* Allocate some space for the user's string on the heap. */ line = malloc(200 * sizeof(char)); if (line == NULL) { printf("Error: malloc failed\n"); exit(1); }
/* Read in a line entered by the user from "standard in". */ printf("Enter a line of text:\n"); line = fgets(line, 200 * sizeof(char), stdin); if (line == NULL) { printf("Error: reading input failed, exiting...\n"); exit(1); } printf("The input line is:\n%s\n", line);
/* Divide the string into tokens. */ token = strtok(line, whitespace); /* get the first token */ while (token != NULL) { printf("Next token is %s\n", token); token = strtok(NULL, whitespace); /* get the next token */ }
// like printf(), the format string allows for placeholders like %d, %f, etc. // pass parameters after the format string to fill them in intsprintf(char *s, constchar *format, ...);
char str[64]; float ave = 76.8; int num = 2;
// initialize str to format string, filling in each placeholder with // a char representation of its arguments' values sprintf(str, "%s is %d years old and in grade %d", "Henry", 12, 7); printf("%s\n", str); // prints: Henry is 12 years old and in grade 7
sprintf(str, "The average grade on exam %d is %g", num, ave); printf("%s\n", str); // prints: The average grade on exam 2 is 76.8
stdlib.h & ctype.h
#include<stdlib.h>// include stdlib and ctypes to use these #include<ctype.h>
intislower(ch); intisupper(ch); // these functions return a non-zero value if the intisalpha(ch); // test is TRUE, otherwise they return 0 (FALSE) intisdigit(ch); intisalnum(ch); intispunct(ch); intisspace(ch); chartolower(ch); // returns ASCII value of lower-case of argument chartoupper(ch);
char str[64]; int len, i;
strcpy(str, "I see 20 ZEBRAS, GOATS, and COWS");
if ( islower(str[2]) ){ printf("%c is lower case\n", str[2]); // prints: s is lower case }
len = strlen(str); for (i = 0; i < len; i++) { if ( isupper(str[i]) ) { str[i] = tolower(str[i]); } elseif( isdigit(str[i]) ) { str[i] = 'X'; } } printf("%s\n", str); // prints: i see XX zebras, goats, and cows
#include<stdlib.h>
intatoi(constchar *nptr); // convert a string to an integer doubleatof(constchar *nptr); // convert a string to a float
Struct
#include<stdio.h> #include<string.h>
/* define a new struct type (outside function bodies) */ structstudentT { char name[64]; int age; float gpa; int grad_yr; };
/* function prototypes */ intcheckID(struct studentT s1, int min_age); voidchangeName(char *old, char *new);
intmain() { int can_vote; // declare variables of struct type: structstudentTstudent1, student2;
// access field values using . strcpy(student1.name, "Ruth"); student1.age = 17; student1.gpa = 3.5; student1.grad_yr = 2021;
// passing a struct field value changeName(student2.name, "Kwame"); printf("student 2's name is now %s\n", student2.name);
return0; }
intcheckID(struct studentT s, int min_age) { int ret = 1;
if (s.age < min_age) { ret = 0; // changes age field IN PARAMETER COPY ONLY s.age = min_age + 1; } return ret; }
voidchangeName(char *old, char *new) { if ((old == NULL) || (new == NULL)) { return; } strcpy(old,new); }
pointer & malloc
structstudentTs; structstudentT *sptr;
// think very carefully about the type of each field when // accessing it (name is an array of char, age is an int ...) strcpy(s.name, "Freya"); s.age = 18; s.gpa = 4.0; s.grad_yr = 2020;
// malloc space for a struct studentT for sptr to point to: sptr = malloc(sizeof(struct studentT)); if (sptr == NULL) { printf("Error: malloc failed\n"); exit(1); }
// the grad_yr field of what sptr points to gets 2021: (*sptr).grad_yr = 2021;
// the age field of what sptr points to gets s.age plus 1: (*sptr).age = s.age + 1;
// the gpa field of what sptr points to gets 3.5: sptr->gpa = 3.5;
// the name field of what sptr points to is a char * // (can use strcpy to init its value): strcpy(sptr->name, "Lars");
Pointer fields in Structs
structpersonT { char *name; // for a dynamically allocated string field int age; };
intmain() { structpersonTp1, *p2;
// need to malloc space for the name field: p1.name = malloc(sizeof(char) * 8); strcpy(p1.name, "Zhichen"); p1.age = 22;
// first malloc space for the struct: p2 = malloc(sizeof(struct personT));
// then malloc space for the name field: p2->name = malloc(sizeof(char) * 4); strcpy(p2->name, "Vic"); p2->age = 19; ...
// Note: for strings, we must allocate one extra byte to hold the // terminating null character that marks the end of the string. }
Array of Structs
structstudentTclassroom1[40];// an array of 40 struct studentT
structstudentT *classroom2;// a pointer to a struct studentT // (for a dynamically allocated array)
structstudentT *classroom3[40];// an array of 40 struct studentT * // (each element stores a (struct studentT *)
Self-Referential Structs
A linked list is one way to implement a list abstract data type.
structnode { int data; // used to store a list element's data value structnode *next;// used to point to the next node in the list };
structnode *head, *temp; int i;
head = NULL; // an empty linked list
head = malloc(sizeof(struct node)); // allocate a node if (head == NULL) { printf("Error malloc\n"); exit(1); } head->data = 10; // set the data field head->next = NULL; // set next to NULL (there is no next element)
// add 2 more nodes to the head of the list: for (i = 0; i < 2; i++) { temp = malloc(sizeof(struct node)); // allocate a node if (temp == NULL) { printf("Error malloc\n"); exit(1); } temp->data = i; // set data field temp->next = head; // set next to point to current first node head = temp; // change head to point to newly added node }
I/O in C
Redirection
# redirect a.out's stdin to read from file infile.txt: $ ./a.out < infile.txt
# redirect a.out's stdout to print to file outfile.txt: $ ./a.out > outfile.txt
# redirect a.out's stdout and stderr to a file out.txt $ ./a.out &> outfile.txt
# redirect all three to different files: # (< redirects stdin, 1> stdout, and 2> stderr): $ ./a.out < infile.txt 1> outfile.txt 2> errorfile.txt
printf
%f, %g: placeholders fora float or double value %d: placeholder fora decimal value (char, short, int) %u: placeholder foran unsigned decimal %c: placeholder fora single character %s: placeholder forastringvalue %p: placeholder to print an address value
%ld: placeholder foralongvalue %lu: placeholder foran unsigned longvalue %lld: placeholder foralonglongvalue %llu: placeholder foran unsigned longlongvalue
%5.3f: print float valueinspace5chars wide, with3 places beyond decimal %20s: print thestringvalueina field of20chars wide, right justified %-20s: print thestringvalueina field of20chars wide, left justified %8d: print the int valueina field of8chars wide, right justified %-8d: print the int valueina field of8chars wide, left justified
%x: print value in hexadecimal (base 16) %o: print value in octal (base 8) %d: print value in signed decimal (base 10) %u: print value in unsigned decimal (unsigned base 10) %e: print float or double in scientific notation (there is no formatting option to display a value in binary)
scanf
int x; float pi;
// read in an int value followed by a float value ("%d%g") // store the int value at the memory location of x (&x) // store the float value at the memory location of pi (&pi) scanf("%d%g", &x, &pi);
getchar & putchar
getchar is particularly useful in C programs that need to support careful error detection and handling of badly formed user input (scanf is not robust in this way).
ch = getchar(); // read in the next char value from stdin putchar(ch); // write the value of ch to stdout
// 2. Open the file: associate the variable with an actual file stream by calling fopen. infile = fopen("input.txt", "r"); // relative path name of file, read mode if (infile == NULL) { printf("Error: unable to open file %s\n", "input.txt"); exit(1); } // fopen with absolute path name of file, write mode outfile = fopen("/home/me/output.txt", "w"); if (outfile == NULL) { printf("Error: unable to open outfile\n"); exit(1); }
// 3. Use I/O operations to read, write, or move the current position in the file int ch; // EOF is not a char value, but is an int. // since all char values can be stored in int, use int for ch ch = getc(infile); // read next char from the infile stream if (ch != EOF) { putc(ch, outfile); // write char value to the outfile stream }
// 4. Close the file: use fclose to close the file when the program no longer needs it fclose(infile); fclose(outfile);
The stdio library also provides functions to change the current position in a file
// to reset current position to beginning of file voidrewind(FILE *f);
rewind(infile);
// to move to a specific location in the file: fseek(FILE *f, long offset, int whence);
fseek(f, 0, SEEK_SET); // seek to the beginning of the file fseek(f, 3, SEEK_CUR); // seek 3 chars forward from the current position fseek(f, -3, SEEK_END); // seek 3 chars back from the end of the file
stdio.h
// --------------- // Character Based // ---------------
// returns the next character in the file stream (EOF is an int value) intfgetc(FILE *f);
// writes the char value c to the file stream f // returns the char value written intfputc(int c, FILE *f);
// pushes the character c back onto the file stream // at most one char (and not EOF) can be pushed back intungetc(int c, FILE *f);
// like fgetc and fputc but for stdin and stdout intgetchar(); intputchar(int c);
// ------------- // String Based // -------------
// reads at most n-1 characters into the array s stopping if a newline is // encountered, newline is included in the array which is '\0' terminated char *fgets(char *s, int n, FILE *f);
// writes the string s (make sure '\0' terminated) to the file stream f intfputs(char *s, FILE *f);
// --------- // Formatted // ---------
// writes the contents of the format string to file stream f // (with placeholders filled in with subsequent argument values) // returns the number of characters printed intfprintf(FILE *f, char *format, ...);
// like fprintf but to stdout intprintf(char *format, ...);
// use fprintf to print stderr: fprintf(stderr, "Error return value: %d\n", ret);
// read values specified in the format string from file stream f // store the read-in values to program storage locations of types // matching the format string // returns number of input items converted and assigned // or EOF on error or if EOF was reached intfscanf(FILE *f, char *format, ...);
// like fscanf but reads from stdin intscanf(char *format, ...);
fscanf Examples
int x; double d; char c, array[MAX];
// write int & char values to file separated by colon with newline at the end fprintf(outfile, "%d:%c\n", x, c);
// read an int & char from file where int and char are separated by a comma fscanf(infile, "%d,%c", &x, &c);
// read a string from a file into array (stops reading at whitespace char) fscanf(infile,"%s", array);
// read a double and a string up to 24 chars from infile fscanf(infile, "%lf %24s", &d, array);
// read in a string consisting of only char values in the specified set (0-5) // stops reading when... // 20 chars have been read OR // a character not in the set is reached OR // the file stream reaches end-of-file (EOF) fscanf(infile, "%20[012345]", array);
// read in a string; stop when reaching a punctuation mark from the set fscanf(infile, "%[^.,:!;]", array);
// read in two integer values: store first in long, second in int // then read in a char value following the int value fscanf(infile, "%ld %d%c", &x, &b, &c);
Command Line Arguments
void * and Type Casting
The C type void * represents a generic pointer — a pointer to any type, or a pointer to an unspecified type.
void *gen_ptr; int x; char ch;
gen_ptr = &x; // gen_ptr can be assigned the address of an int gen_ptr = &ch; // or the address of a char (or the address of any type)
Two Usages
A call to malloc recasts its void * return type to the specific pointer type of the variable used to store its returned heap memory address
Students often encounter the void * when creating threads. Using a void * parameter type in a thread function allows the thread to take any type of application-specific pointer.
/* * an application-specific pthread main function * must have this function prototype: int func_name(void *args) * * any given implementation knows what type is really passed in * args: pointer to an int value */ intmy_thr_main(void *args) { int num;
// first recast args to an int *, then dereference to get int value num = *((int *)args); // num gets 6 ... }
intmain() { int ret, x; pthread_t tid;
x = 6; // pass the address of int variable (x) to pthread_create's void * param // (we recast &x as a (void *) to match the type of pthread_create's param) ret = pthread_create(&tid, NULL, my_thr_main, // a thread main function (void *)(&x)); // &x will be passed to my_thr_main // ...
C Libraries
A C library consists of two parts:
The application programming interface (API) to the library, which gets defined in one or more header files (.h files) that must be included in C source code files that plan to use the library.
The implementation of the library’s functionality, often made available to programs in a precompiledbinary format that gets linked (added) into the binary executable created by gcc. Precompiled library code might be in an archive file (libsomelib.a) containing several .o files that can be statically linked into the executable file at compile time. Alternatively, it may consist of a shared object file (libsomelib.so) that can be dynamically linked at runtime into a running program.
Large C programs tend to use many C libraries, some of which gcc links implicitly, whereas others require explicit linking with the -l command line option to gcc. $ gcc -o myprog myprog.c -lpthread -lreadline
It also allows the compiler to choose to dynamically link when both a shared object (.so) and an archive (.a) version of a library are available.
If users want to statically link libraries, then they can explicitly specify static linking in the gcc command line. The --static option provides one method for requesting static linking $ gcc -o myprog myprog.c --static -lpthread -lreadline
struct & union
struct
Usage:
Serialization of data
Passing multiple arguments in and out of functions through a single argument
Data structures such as linked lists, binary trees, and more
// Definition structTag { Members };
// Create a single structure structTagname_of_single_structure;
structPerson { ... } person1, person2, p[20];
// access a variable of the structure name_of_single_structure.name_of_variable;
// it can be a return type of a function struct database fn();
typedef
Typedefs allow us to define types with a different name - which can come in handy when dealing with structs and pointers.
typedefstruct { int x; int y; } point;
point p;
Nested Structures
structcomplex { int imag; float real; };
structnumber { structcomplexcomp; int integers; } num1, num2;
num2.comp.imag = 11;
union
Unions are like structures except that all the variables share the same memory. When a union is declared the compiler allocates enough memory for the largest data-type in the union. It’s like a giant storage chest where you can store one large item, or a small item, but never the both at the same time
Alignment & Padding
Alignment
Each type except char has an alignment requirement. Signed or unsigned makes no difference.
**1-byte **chars can start on any byte address
2-byteshorts must start on an even address,
4-byteints or floats must start on an address divisible by 4
8-bytelongs or doubles must start on an address divisible by 8.
Self-alignment makes access faster because it facilitates generating single-instruction fetches and puts of the typed data.
Padding
// initially char *p; char c; int x;
// Equal to char *p; /* 4 or 8 bytes */ char c; /* 1 byte */ char pad[3]; /* 3 bytes */ int x; /* 4 bytes */
Structure Alignment & Padding
structfoo1 { char *p; char c; long x; };
// is equivalent to structfoo1 { char *p; /* 8 bytes */ char c; /* 1 byte char pad[7]; /* 7 bytes */ long x; /* 8 bytes */ };
structfoo6 { short s; char c; int flip:1; int nybble:4; int septet:7; };
// is equal to structfoo6 { short s; /* 2 bytes */ char c; /* 1 byte */ int flip:1; /* total 1 bit */ int nybble:4; /* total 5 bits */ int pad1:3; /* pad to an 8-bit boundary */ int septet:7; /* 7 bits */ int pad2:25; /* pad to 32 bits */ };