├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── LICENSE ├── Makefile ├── PHILOSOPHY.md ├── README.md ├── TODO.md ├── ch-1 ├── Makefile ├── README.md ├── include │ ├── 1.11.h │ ├── 1.13.h │ ├── 1.14.h │ ├── 1.15.h │ ├── 1.16.h │ ├── 1.19.h │ ├── 1.20.h │ ├── 1.21.h │ ├── 1.22.h │ └── 1.24.h └── src │ ├── 1.10.c │ ├── 1.11.c │ ├── 1.12.c │ ├── 1.13.c │ ├── 1.14.c │ ├── 1.15.c │ ├── 1.16.c │ ├── 1.17.c │ ├── 1.18.c │ ├── 1.19.c │ ├── 1.20.c │ ├── 1.21.c │ ├── 1.22.c │ ├── 1.23.c │ ├── 1.24.c │ ├── 1.3-4.c │ ├── 1.5.c │ ├── 1.6.c │ ├── 1.7.c │ ├── 1.8.c │ └── 1.9.c ├── ch-2 ├── Makefile ├── include │ ├── 2.10.h │ ├── 2.2.h │ ├── 2.3.h │ ├── 2.4.h │ ├── 2.5.h │ ├── 2.6.h │ ├── 2.7.h │ ├── 2.8.h │ └── 2.9.h └── src │ ├── 2.1.c │ ├── 2.10.c │ ├── 2.2.c │ ├── 2.3.c │ ├── 2.4.c │ ├── 2.5.c │ ├── 2.6.c │ ├── 2.7.c │ ├── 2.8.c │ └── 2.9.c ├── ch-3 ├── Makefile ├── include │ ├── 3.1.h │ ├── 3.2.h │ └── 3.3.h └── src │ ├── 3.1.c │ ├── 3.2.c │ ├── 3.3.c │ ├── 3.4.c │ ├── 3.5.c │ └── 3.6.c ├── ch-4 ├── Makefile ├── README.md ├── rpc │ ├── README.md │ ├── include │ │ └── rpc.h │ └── src │ │ ├── lexer.c │ │ ├── main.c │ │ ├── parser.c │ │ ├── rpn_math.c │ │ ├── stack.c │ │ └── vars.c └── src │ ├── 4.1.c │ ├── 4.11.c │ ├── 4.12-13.c │ ├── 4.14.c │ └── 4.2.c ├── ch-5 ├── Makefile ├── README.md ├── decl │ ├── README.md │ ├── decl-test.sh │ ├── decl-test.txt │ ├── include │ │ └── decl.h │ ├── src │ │ ├── common │ │ │ ├── decl.c │ │ │ ├── dirdecl.c │ │ │ ├── gettoken.c │ │ │ └── vars.c │ │ ├── decl-main.c │ │ └── undecl-main.c │ └── undecl-test.sh ├── sort │ ├── README.md │ ├── include │ │ ├── sort-tests.h │ │ └── sort.h │ ├── sort-test-homedir.txt │ ├── sort-test-mixed-case.txt │ ├── sort-test-subfields.txt │ ├── sort-test.txt │ ├── sorted.txt │ └── src │ │ ├── compare.c │ │ ├── input.c │ │ ├── main.c │ │ ├── sort.c │ │ ├── strings.c │ │ └── tests.c.bak ├── src │ ├── 5.1.c │ ├── 5.10.c │ ├── 5.2.c │ ├── 5.3.c │ ├── 5.4.c │ ├── 5.5.c │ ├── 5.6.c │ ├── 5.7.c │ └── 5.8-9.c ├── tabber │ ├── README.md │ ├── include │ │ └── tabber.h │ ├── src │ │ ├── detab.c │ │ ├── entab.c │ │ ├── input.c │ │ ├── main.c │ │ └── misc.c │ └── tabber-test.sh └── tail │ ├── include │ └── tail.h │ ├── src │ ├── main.c │ └── queue.c │ └── tail-test.sh ├── ch-6 ├── Makefile ├── README.md ├── charmatch │ ├── README.md │ ├── charmatch-test.c │ ├── include │ │ └── charmatch.h │ └── src │ │ ├── fsm.c │ │ ├── main.c │ │ ├── misc.c │ │ ├── strings.c │ │ ├── types.c │ │ └── vars.c ├── crossref │ ├── README.md │ ├── crossref-test-full.txt │ ├── crossref-test-part.txt │ ├── include │ │ └── crossref.h │ └── src │ │ ├── io.c │ │ ├── main.c │ │ ├── node.c │ │ └── tree.c ├── hashtable │ ├── README.md │ ├── hashtable-test.c │ ├── include │ │ └── hashtable.h │ └── src │ │ ├── hashtable.c │ │ ├── main.c │ │ ├── mem.c │ │ └── preprocessor.c ├── kwcount │ ├── kwcount-test.c │ └── src │ │ └── kwcount.c └── wordcount │ ├── README.md │ ├── include │ └── wordcount.h │ ├── src │ ├── compare.c │ ├── display.c │ ├── main.c │ ├── mem.c │ └── tree.c │ ├── wordcount-test-1.txt │ └── wordcount-test.txt ├── ch-7 ├── Makefile ├── README.md └── src │ ├── 7.1.c │ ├── 7.2.c │ ├── 7.3.c │ ├── 7.4.c │ ├── 7.5.c │ ├── 7.6.c │ ├── 7.7.c │ ├── 7.8.c │ └── 7.9.c ├── ch-8 ├── Makefile ├── fopen_j │ ├── README.md │ ├── include │ │ └── fopen_j.h │ └── src │ │ ├── buffering.c │ │ ├── fclose.c │ │ ├── fopen.c │ │ ├── fseek.c │ │ ├── io.c │ │ └── main.c ├── include │ └── 8.5.h ├── malloc_j │ ├── README.md │ ├── include │ │ └── malloc_j.h │ └── src │ │ ├── free_j.c │ │ ├── init.c │ │ ├── malloc_j.c │ │ └── test.c └── src │ ├── 8.1.c │ └── 8.5.c ├── common ├── include │ ├── common.h │ ├── tests.h │ └── trees.h └── src │ ├── bitwise.c │ ├── character.c │ ├── line.c │ ├── maths.c │ ├── memory.c │ ├── strings.c │ ├── tests.c │ └── trees.c ├── faq ├── .gitignore ├── Makefile ├── PHILOSOPHY.md ├── README.md ├── arithmetic-ops-with-pointers.c ├── arrays-of-arrays.c ├── assign-to-assignment.c ├── fucking-for-loops.c ├── initialization.c ├── initializing-string-literals.c ├── keywords │ └── README.md ├── long-lines.c ├── negative-integers.c ├── null-terminators.c ├── print-empty-arrays.c ├── printing-signed.c ├── returning-structs.c ├── single-quotes.c ├── strcmp.c ├── struct-alignment-bits.c ├── type-sizes.c ├── typedef-struct.c └── unsigned-overflow.c └── makefiles ├── building.mk └── docker.mk /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | **/calculator 39 | 40 | # Debug files 41 | *.dSYM/ 42 | *.su 43 | *.idb 44 | *.pdb 45 | 46 | # Kernel Module Compile Results 47 | *.mod* 48 | *.cmd 49 | .tmp_versions/ 50 | modules.order 51 | Module.symvers 52 | Mkfile.old 53 | dkms.conf 54 | 55 | # Compiled Executables 56 | out 57 | 58 | # Other 59 | **/.DS_Store 60 | **/*.pdf 61 | **/bin/* 62 | **/.vscode/* 63 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnPaste": true, 3 | "editor.formatOnSave": true, 4 | "C_Cpp.clang_format_fallbackStyle": "Google", 5 | "C_Cpp.clang_format_path": "/usr/local/bin/clang-format", 6 | "files.associations": { 7 | "5-common.h": "c", 8 | "stdio.h": "c", 9 | "5.2.h": "c", 10 | "limits.h": "c", 11 | "climits": "c", 12 | "common.h": "c", 13 | "cmocka.h": "c", 14 | "stdarg.h": "c", 15 | "1.14.h": "c", 16 | "1.15.h": "c", 17 | "initializer_list": "c", 18 | "string_view": "c", 19 | "utility": "c", 20 | "malloc_j.h": "c", 21 | "mman.h": "c", 22 | "cerrno": "c", 23 | "*.tcc": "c", 24 | "limits": "c", 25 | "string.h": "c" 26 | }, 27 | "files.insertFinalNewline": true, 28 | "editor.tabSize": 2, 29 | "C_Cpp.intelliSenseEngine": "Tag Parser" 30 | } 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Contains compile / testing settings 2 | include ./makefiles/building.mk 3 | 4 | # Targets for running a Dockerized testing environment 5 | include ./makefiles/docker.mk 6 | 7 | # Individual chapter Makefiles 8 | include ch-1/Makefile ch-2/Makefile ch-3/Makefile ch-4/Makefile 9 | include ch-5/Makefile ch-6/Makefile ch-7/Makefile ch-8/Makefile 10 | 11 | ### Individual exercise targets 12 | .PHONY: ch-1 ch-2 ch-3 ch-4 ch-5 ch-6 ch-7 ch-8 13 | 14 | all: ch-1 ch-2 ch-3 ch-4 ch-5 ch-6 ch-7 ch-8 15 | ch-1: 1.3-4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13 1.14 16 | ch-1: 1.15 1.16 1.17 1.18 1.19 1.20 1.21 1.22 1.23 1.24 17 | ch-2: 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 18 | ch-3: 3.1 3.2 3.3 3.4 3.5 3.6 19 | ch-4: 4.1 4.2 rpc 4.11 4.12-13 4.14 20 | ch-5: 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8-9 5.10 tabber 21 | ch-5: tail sort decl undecl 22 | ch-6: kwcount charmatch crossref wordcount hashtable 23 | ch-7: 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 24 | ch-8: cat fopen_j 8.5 malloc_j 25 | -------------------------------------------------------------------------------- /PHILOSOPHY.md: -------------------------------------------------------------------------------- 1 | # Unix Philosophy 2 | * Rule of Modularity: Write simple parts connected by clean interfaces. 3 | * Rule of Clarity: Clarity is better than cleverness. 4 | * Rule of Composition: Design programs to be connected to other programs. 5 | * Rule of Separation: Separate policy from mechanism; separate interfaces from engines. 6 | * Rule of Simplicity: Design for simplicity; add complexity only where you must. 7 | * Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do. 8 | * Rule of Transparency: Design for visibility to make inspection and debugging easier. 9 | * Rule of Robustness: Robustness is the child of transparency and simplicity. 10 | * Rule of Representation: Fold knowledge into data so program logic can be stupid and robust. 11 | * Rule of Least Surprise: In interface design, always do the least surprising thing. 12 | * Rule of Silence: When a program has nothing surprising to say, it should say nothing. 13 | * Rule of Repair: When you must fail, fail noisily and as soon as possible. 14 | * Rule of Economy: Programmer time is expensive; conserve it in preference to machine time. 15 | * Rule of Generation: Avoid hand-hacking; write programs to write programs when you can. 16 | * Rule of Optimization: Prototype before polishing. Get it working before you optimize it. 17 | * Rule of Diversity: Distrust all claims for “one true way”. 18 | * Rule of Extensibility: Design for the future, because it will be here sooner than you think. 19 | 20 | [Source](http://www.catb.org/esr/writings/taoup/html/ch01s06.html) 21 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - Ch. 6 4 | - Handle typedef / define in charmatch 5 | - Appendix A 6 | - Reading 7 | - Appendix B 8 | - Reading 9 | - Appendix C 10 | - Reading 11 | -------------------------------------------------------------------------------- /ch-1/Makefile: -------------------------------------------------------------------------------- 1 | 1.3-4: 1.3-4-build 1.3-4-basic-test 2 | 3 | 1.5: 1.5-build 1.5-basic-test 4 | 5 | 1.6: 1.6-build 6 | ifdef RUN_TESTS 7 | printf "d" | $(VALGRIND) ./bin/$@ 8 | cat EOF | $(VALGRIND) ./bin/$@ 9 | endif 10 | 11 | 1.7: 1.7-build 1.7-basic-test 12 | 13 | 1.8: 1.8-build 14 | ifdef RUN_TESTS 15 | printf "this program prints whitespace " | $(VALGRIND) ./bin/$@ 16 | endif 17 | 18 | 1.9: 1.9-build 19 | ifdef RUN_TESTS 20 | printf "this program strips extra spaces" | $(VALGRIND) ./bin/$@ 21 | endif 22 | 23 | 1.10: 1.10-build 24 | ifdef RUN_TESTS 25 | printf "this program escapes spaces and tabs" | $(VALGRIND) ./bin/$@ 26 | endif 27 | 28 | 1.11: 1.11-build 1.11-basic-test 29 | 30 | 1.12: 1.12-build 31 | ifdef RUN_TESTS 32 | printf "this program prints every word in input" | $(VALGRIND) ./bin/$@ 33 | endif 34 | 35 | 1.13: 1.13-build 36 | ifdef RUN_TESTS 37 | printf "this this this program program prints a a a a histogram" | $(VALGRIND) ./bin/$@ 38 | endif 39 | 40 | 1.14: 1.14-build 41 | ifdef RUN_TESTS 42 | printf "this program prints a char frequency histogram" | $(VALGRIND) ./bin/$@ 43 | endif 44 | 45 | 1.15: 1.15-build 1.15-basic-test 46 | 47 | 1.16: 1.16-build 48 | ifdef RUN_TESTS 49 | printf "this\nprogram prints\nthe longest line" | $(VALGRIND) ./bin/$@ 50 | endif 51 | 52 | 1.17: 1.17-build 53 | ifdef RUN_TESTS 54 | printf "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" | $(VALGRIND) ./bin/$@ 55 | printf "Not longer than 80" | $(VALGRIND) ./bin/$@ 56 | endif 57 | 58 | 1.18: 1.18-build 59 | ifdef RUN_TESTS 60 | printf "removes trailing whitespace " | $(VALGRIND) ./bin/$@ 61 | endif 62 | 63 | 1.19: 1.19-build 1.19-basic-test 64 | 65 | 1.20: 1.20-build 66 | ifdef RUN_TESTS 67 | printf "detabs\tlines\t" | $(VALGRIND) ./bin/$@ 68 | endif 69 | 70 | 1.21: 1.21-build 71 | ifdef RUN_TESTS 72 | printf "tab stops every 5 chars" | $(VALGRIND) ./bin/$@ 73 | endif 74 | 75 | 1.22: 1.22-build 76 | ifdef RUN_TESTS 77 | printf "01234567890123456789" | $(VALGRIND) ./bin/$@ 78 | endif 79 | 80 | 1.23: 1.23-build 81 | ifdef RUN_TESTS 82 | printf "// remove comments\nvoid main(){return 0;}" | $(VALGRIND) ./bin/$@ 83 | endif 84 | 85 | 1.24: 1.24-build 86 | ifdef RUN_TESTS 87 | printf "// remove comments\nvoid main(){return 0;}" | $(VALGRIND) ./bin/$@ 88 | printf "// remove comments\nvoid main(){return 0;}}" | $(VALGRIND) ./bin/$@ 89 | endif 90 | -------------------------------------------------------------------------------- /ch-1/README.md: -------------------------------------------------------------------------------- 1 | Ch-1 2 | --- 3 | 4 | ## Notes about the exercises 5 | * 1.1, 1.2: 6 | * These did not have any demo-able code; the instructions were just `experiment with hello world`. 7 | -------------------------------------------------------------------------------- /ch-1/include/1.11.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | int lines; 3 | int words; 4 | int chars; 5 | } counter; 6 | 7 | void word_count(const char* const input, const int len, counter* const counts); 8 | -------------------------------------------------------------------------------- /ch-1/include/1.13.h: -------------------------------------------------------------------------------- 1 | void get_words(int* const words); 2 | void print_histogram(int* const words); 3 | -------------------------------------------------------------------------------- /ch-1/include/1.14.h: -------------------------------------------------------------------------------- 1 | void get_chars(int* const words); 2 | void print_histogram(const char* const words, int* const char_counts); 3 | -------------------------------------------------------------------------------- /ch-1/include/1.15.h: -------------------------------------------------------------------------------- 1 | double fahr_to_cels(const double fahr); 2 | double cels_to_fahr(const double cels); 3 | -------------------------------------------------------------------------------- /ch-1/include/1.16.h: -------------------------------------------------------------------------------- 1 | void copy(char* const to, const char* const from); 2 | int longest_line(void); 3 | -------------------------------------------------------------------------------- /ch-1/include/1.19.h: -------------------------------------------------------------------------------- 1 | void swap(char* const string, const int a, const int b); 2 | -------------------------------------------------------------------------------- /ch-1/include/1.20.h: -------------------------------------------------------------------------------- 1 | char* detab(const char* const in_line, const int in_len, const int tab_stop); 2 | -------------------------------------------------------------------------------- /ch-1/include/1.21.h: -------------------------------------------------------------------------------- 1 | char* entab(const char* const in_line, const int in_len, const int tab_stop); 2 | int look_ahead(const char* const in_line, const int in_len, int offset, 3 | const int tab_stop); 4 | -------------------------------------------------------------------------------- /ch-1/include/1.22.h: -------------------------------------------------------------------------------- 1 | void fold(const char* const line, const int len, const int column); 2 | -------------------------------------------------------------------------------- /ch-1/include/1.24.h: -------------------------------------------------------------------------------- 1 | // store for syntactically relevant symbols 2 | typedef struct ir { 3 | int parens; // number of ( or ) 4 | int curlys; // number of { or } 5 | int squares; // number of [ or ] 6 | int comment_braces; // number of / 7 | } input_registry; 8 | 9 | // states for parser FSM 10 | typedef struct ps { 11 | int in_single_comment; // are we in a // comment? 12 | int in_multi_comment; // are we in a /* comment? 13 | int in_single_quote; // are we in a 'single quote'? 14 | int in_double_quote; // are we in "double quotes"? 15 | int should_parse; // should we count any symbols we see? 16 | } parsing_state; 17 | 18 | void parsing_test(const int c, parsing_state* const ps); 19 | void input_test(const int c1, input_registry* const ir); 20 | void print_output(const input_registry* const ir, 21 | const parsing_state* const ps); 22 | -------------------------------------------------------------------------------- /ch-1/src/1.10.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef DEBUG 4 | #pragma clang diagnostic ignored "-Wunused-function" 5 | #endif 6 | 7 | /* 8 | * Ex 1.10: Write a program to copy its input to its output, replacing each tab 9 | * by \t, each backspace by \b, and each backslash by \\. This makes tabs and 10 | * backspaces visible in an unambiguous way. 11 | * 12 | * Note: \b doesn't work on my system, as backspacing just deletes characters up 13 | * to an empty string, so I'll escape space characters instead. 14 | */ 15 | 16 | static int replace_with_escape() { 17 | int c; 18 | 19 | while ((c = getchar()) != EOF) { 20 | switch (c) { 21 | case '\\': 22 | printf("\\\\"); 23 | break; 24 | case '\t': 25 | printf("\\t"); 26 | break; 27 | case ' ': 28 | printf("\\w"); 29 | break; 30 | default: 31 | putchar(c); 32 | break; 33 | } 34 | } 35 | putchar('\n'); 36 | return 0; 37 | } 38 | 39 | int main() { return replace_with_escape(); } 40 | -------------------------------------------------------------------------------- /ch-1/src/1.12.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef DEBUG 4 | #pragma clang diagnostic ignored "-Wunused-function" 5 | #endif 6 | 7 | // Ex 1.12: Write a program that prints every word of input on its own line 8 | 9 | static int print_every_word() { 10 | int c; 11 | 12 | while ((c = getchar()) != EOF) { 13 | if (c == ' ') 14 | putchar('\n'); 15 | else { 16 | putchar(c); 17 | } 18 | } 19 | putchar('\n'); 20 | return 0; 21 | } 22 | 23 | int main() { return print_every_word(); } 24 | -------------------------------------------------------------------------------- /ch-1/src/1.13.c: -------------------------------------------------------------------------------- 1 | #include "1.13.h" 2 | #include 3 | 4 | /* 5 | * Ex 1.13: Write a program to print a histogram of words lengths in its input; 6 | * vertical is harder than horizontal. 7 | */ 8 | 9 | #define IN 1 // inside a word 10 | #define OUT 0 // outside a word 11 | 12 | int main() { 13 | int words[10] = {0}; // Initializes all elements to zero 14 | get_words(words); 15 | print_histogram(words); 16 | return 0; 17 | } 18 | 19 | // get_words(): reads whitespace separated words from stdin until EOF 20 | void get_words(int* const words) { 21 | int c, word_length, state; 22 | word_length = 0; 23 | state = OUT; 24 | 25 | printf("Begin typing, terminate via ctrl-D on an empty line.\n"); 26 | while ((c = getchar()) != EOF) { 27 | if (c == ' ' || c == '\n' || c == '\t') { 28 | state = OUT; 29 | if (word_length >= 10) { 30 | words[9]++; 31 | } else if (word_length >= 1) { 32 | words[word_length - 1]++; 33 | word_length = 0; 34 | } 35 | } else if (state == OUT) 36 | state = IN; 37 | if (state == IN) ++word_length; 38 | } 39 | if (state == IN) { // catches edge case where user hits CTRL-D mid-word. 40 | words[word_length - 1]++; 41 | } 42 | } 43 | 44 | // print_histogram(): prints a histogram of frequencies of words by length 45 | void print_histogram(int* const words) { 46 | int i = 0; 47 | int curr_column, more_rows; 48 | 49 | // Print the header 50 | for (i = 0; i < 9; i++) { 51 | printf("%4d", i + 1); 52 | } 53 | printf("%4d+\n", 10); 54 | 55 | // Print the actual rows; decrement 56 | // each nonzero entry in words[] and print 57 | // spaced X characters until all entries are zero 58 | more_rows = 1; 59 | while (more_rows) { 60 | more_rows = 0; 61 | for (curr_column = 0; curr_column < 10; curr_column++) { 62 | if (words[curr_column] != 0) { 63 | more_rows = 1; 64 | printf("%4c", 'X'); 65 | words[curr_column]--; 66 | } else { 67 | printf("%4c", ' '); 68 | } 69 | } 70 | printf("\n"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ch-1/src/1.14.c: -------------------------------------------------------------------------------- 1 | #include "1.14.h" 2 | #include 3 | #include 4 | 5 | /* 6 | * Ex 1.14: Write a program to print a histogram 7 | * of the frequencies of different characters in its input. 8 | */ 9 | 10 | int main() { 11 | int char_counts[26] = {0}; // Initializes all elements to zero 12 | char characters[26] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 13 | 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 14 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 15 | get_chars(char_counts); 16 | print_histogram(characters, char_counts); 17 | return 0; 18 | } 19 | 20 | // get_chars(): reads character-by-character input until EOF from stdin 21 | void get_chars(int* const char_counts) { 22 | int c; 23 | 24 | while ((c = getchar()) != EOF) { 25 | if (c >= 'a' && c <= 'z') { 26 | char_counts[c - 'a']++; 27 | } else if (c >= 'A' && c <= 'Z') { 28 | char_counts[tolower(c) - 'a']++; 29 | } 30 | } 31 | } 32 | 33 | // print_histogram(): prints a histogram of character frequencies from 34 | // array args. 35 | void print_histogram(const char* const characters, int* const char_counts) { 36 | int i = 0; 37 | int curr_column, more_rows; 38 | 39 | // Print the header 40 | for (i = 0; i < 26; i++) { 41 | printf("%4c", characters[i]); 42 | } 43 | printf("\n"); 44 | 45 | // Print the actual rows; decrement 46 | // each nonzero entry in words[] and print 47 | // spaced X characters until all entries are zero 48 | more_rows = 1; 49 | while (more_rows) { 50 | more_rows = 0; 51 | for (curr_column = 0; curr_column < 26; curr_column++) { 52 | if (char_counts[curr_column] != 0) { 53 | more_rows = 1; 54 | printf("%4c", 'X'); 55 | char_counts[curr_column]--; 56 | } else { 57 | printf("%4c", ' '); 58 | } 59 | } 60 | printf("\n"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ch-1/src/1.15.c: -------------------------------------------------------------------------------- 1 | #include "1.15.h" 2 | #include 3 | #include "tests.h" 4 | 5 | /* 6 | * Ex 1.15: Rewrite the temperature conversion program of Section 1.2 to use a 7 | * function for conversion. 8 | */ 9 | 10 | static void test_f_to_c(const double f, const double expected, 11 | const char* const message) { 12 | const double actual = fahr_to_cels(f); 13 | assert_double_eq(actual, expected, .1, "fahr_to_cels", message); 14 | } 15 | 16 | static void test_c_to_f(const double c, const double expected, 17 | const char* const message) { 18 | const double actual = cels_to_fahr(c); 19 | assert_double_eq(actual, expected, .1, "cels_to_fahr", message); 20 | } 21 | 22 | int main() { 23 | printf("1.15: Running tests...\n"); 24 | test_f_to_c(212.0, 100.0, "normal input"); 25 | test_f_to_c(0.0, -17.777, "0.0 input"); 26 | test_f_to_c(-10.0, -23.333, "negative input"); 27 | test_c_to_f(100.0, 212.0, "normal input"); 28 | test_c_to_f(0.0, 32.0, "0.0 input"); 29 | test_c_to_f(-17.777, 0.0, "negative input"); 30 | printf("1.15: PASS!\n"); 31 | 32 | return 0; 33 | } 34 | 35 | // fahr_to_cels() / cels_to_fahr(): convert temperatures from C to F or vice 36 | // versa 37 | double fahr_to_cels(const double fahr) { return 5.0 / 9.0 * (fahr - 32.0); } 38 | double cels_to_fahr(const double cels) { return 32.0 + (cels * (9.0 / 5.0)); } 39 | -------------------------------------------------------------------------------- /ch-1/src/1.16.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "1.16.h" 4 | #include "common.h" 5 | 6 | /* 7 | * 1.16 Revise the main routine of the longest-line program so it will correctly 8 | * print the length of arbitrarily long input lines, and as much as possible of 9 | * the text. 10 | * 11 | * Note: if multiple strings are the longest, this program prints the first one. 12 | */ 13 | 14 | int main() { return longest_line(); } 15 | 16 | int longest_line() { 17 | int len; // current line length 18 | int max; // maximum length seen so far 19 | char line[MAXLEN]; // current input line 20 | char longest[MAXLEN]; // longest line saved here 21 | 22 | max = 0; 23 | while ((len = mygetline(line, MAXLEN)) > 0) 24 | if (len > max) { 25 | max = len; 26 | copy(longest, line); 27 | } 28 | if (max > 0) { // there was a line 29 | printf("%d: %s", max - 1, longest); 30 | } 31 | printf("\n"); 32 | return 0; 33 | } 34 | 35 | // copy(): copy 'from' into 'to'; assumes 'to' is big enough 36 | void copy(char* const to, const char* const from) { 37 | int i; 38 | 39 | i = 0; 40 | while ((to[i] = from[i]) != '\0') { 41 | ++i; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ch-1/src/1.17.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | 5 | /* 6 | * Ex 1.17: Write a program to print all input lines that are longer than 80 7 | * characters. 8 | */ 9 | 10 | static int longer_than_80() { 11 | int len; // current line length 12 | char line[MAXLEN]; // current input line 13 | 14 | while ((len = mygetline(line, MAXLEN)) > 0) 15 | if (len > 80) { 16 | printf("%d: %s", len - 1, line); 17 | } 18 | return 0; 19 | } 20 | 21 | int main() { return longer_than_80(); } 22 | -------------------------------------------------------------------------------- /ch-1/src/1.18.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common.h" 3 | 4 | /* 5 | Ex 1.18: Write a program to remove trailing blanks and tabs from each 6 | line of input, and to delete entirely blank lines. 7 | */ 8 | 9 | static int remove_trailing() { 10 | int len, last; // current line length, and string index 11 | char line[MAXLEN]; // current input line 12 | 13 | while ((len = mygetline(line, MAXLEN)) > 0) { 14 | last = len - 2; // remove \n\0 15 | while (((line[last] == ' ') || (line[last] == '\t')) && len >= 0) { 16 | line[last] = '\n'; 17 | last--; 18 | } 19 | line[last + 2] = '\0'; // if no trailing spaces, line[len] is the null 20 | // terminator so this is safe 21 | 22 | if (last > 0) { 23 | printf("%s", line); 24 | } 25 | } 26 | return 0; 27 | } 28 | 29 | int main() { return remove_trailing(); } 30 | -------------------------------------------------------------------------------- /ch-1/src/1.19.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "1.19.h" 6 | #include "common.h" 7 | #include "tests.h" 8 | 9 | /* 10 | * Ex 1.19: Write a function reverse(s) that reverses the character 11 | * string s. Use it to write a program that reverses its input a line 12 | * at a time. 13 | */ 14 | 15 | static void test(const char* const str, const char* const expected, 16 | const char* const message) { 17 | const int len = (int)strlen(str); 18 | char* actual = (char*)malloc((unsigned long)len + 1); 19 | strcpy(actual, str); 20 | reverse(actual, len); 21 | assert_string_eq(actual, expected, "reverse", message); 22 | free(actual); 23 | } 24 | 25 | int main() { 26 | printf("1.19: Running tests...\n"); 27 | test("Joshua", "auhsoJ", "expected input"); 28 | test("", "", "empty input"); 29 | test("a", "a", "single char"); 30 | printf("1.19: PASS!\n"); 31 | 32 | return 0; 33 | } 34 | 35 | // swap(): exchange string[a] with string[b] 36 | void swap(char* const string, const int a, const int b) { 37 | char temp; 38 | temp = string[a]; 39 | string[a] = string[b]; 40 | string[b] = temp; 41 | } 42 | -------------------------------------------------------------------------------- /ch-1/src/1.20.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "1.20.h" 6 | #include "common.h" 7 | #include "tests.h" 8 | 9 | /* 10 | * Ex 1.20: Write a program detab() that replaces tabs in the input with the 11 | * proper number of blanks to space to the next tab stop. Assume a fixed set of 12 | * tab stops, say every n columns. Should n be a variable or a symbolic 13 | * parameter? 14 | * --- 15 | * I am not sure what "symbolic parameter" means here, but a "tab stop" means 16 | * the following: suppose that you declare every 10 columns to be a tab stop. 17 | * Then entering 5 characters and pressing tab causes columns 1 through 5 to be 18 | * populated with keystroke characters, and columns 6 through 10 to be populated 19 | * with whitespaces. Entering another 9 characters and pressing tab populates 20 | * columns 11 through 19 with characters and column 20 with a whitespace. 21 | */ 22 | 23 | #define WHITESPACE '.' 24 | 25 | int main() { 26 | int len; // current line length, and string index 27 | char line[MAXLEN]; // current input line 28 | char* tabless_line; 29 | 30 | printf("Begin entering text; whitespace will be printed as '.'\n"); 31 | while ((len = mygetline(line, MAXLEN)) > 0) { 32 | tabless_line = detab(line, len, 5); 33 | printf("%s", tabless_line); 34 | free(tabless_line); 35 | } 36 | return 0; 37 | } 38 | 39 | // detab(): replace tabs in in_line with with 40 | // whitespace up to next tab_stop-th column 41 | char* detab(const char* const in_line, const int in_len, const int tab_stop) { 42 | char* out_line; 43 | int i, j; 44 | char temp[MAXLEN]; 45 | 46 | // go through in_line until a tab is found and convert 47 | // to appropriate number of whitespace up to tab stop 48 | for (j = i = 0; i < in_len; i++) { 49 | if (in_line[i] != '\t') { 50 | temp[j] = in_line[i]; 51 | j++; 52 | } else { 53 | while (j % tab_stop) { 54 | temp[j] = WHITESPACE; 55 | j++; 56 | } 57 | } 58 | } 59 | temp[j] = '\0'; 60 | 61 | out_line = (char*)malloc((unsigned long)j + 1); 62 | strncpy(out_line, temp, (unsigned long)j + 1); 63 | return out_line; 64 | } 65 | -------------------------------------------------------------------------------- /ch-1/src/1.22.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "1.22.h" 4 | #include "common.h" 5 | 6 | /* 7 | * Ex 1.22: Write a program to "fold" long input lines into two or more shorter 8 | * lines after the last non-blank character that occurs before the n-th column 9 | * of input. Make sure your program does something intelligent with very long 10 | * lines, and if there are no blanks or tabs before the specified column. 11 | * ---- 12 | * I'm assuming this question is asking me to replicate the $fold program. 13 | */ 14 | 15 | #define FOLD_COLUMN 5 16 | 17 | int main() { 18 | char line[MAXLEN]; 19 | int len; 20 | while ((len = mygetline(line, MAXLEN))) { 21 | fold(line, MAXLEN, FOLD_COLUMN); 22 | } 23 | 24 | return 0; 25 | } 26 | 27 | void fold(const char* const line, const int len, const int column) { 28 | if (column == 0) { 29 | printf("Error - cannot fold to 0th column.\n"); 30 | return; 31 | } 32 | for (int i = 0; i < len && line[i] != '\0'; i++) { 33 | if (i > 0 && !(i % column)) { 34 | printf("\n%c", line[i]); 35 | } else { 36 | printf("%c", line[i]); 37 | } 38 | } 39 | printf("\n"); 40 | } 41 | -------------------------------------------------------------------------------- /ch-1/src/1.23.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Ex 1.23: Write a program to remove all comments from a C program. Don't 5 | * forget to handle quoted strings and character constants properly. C comments 6 | * do not nest. 7 | */ 8 | 9 | static int remove_comments() { 10 | int in_single_comment, in_multi_comment; /* this gets removed */ 11 | in_single_comment = in_multi_comment = 0; 12 | int c, temp; 13 | 14 | while ((c = getchar()) != EOF) { 15 | /* 16 | Check if we're in a single-line comment. There's two edge cases here: 17 | 1 - legitimate divison operator, which should be printed. 18 | 2 - backslashes inside of comments, which should not be. 19 | */ 20 | if ((c == '/') && (!in_single_comment && !in_multi_comment)) { 21 | temp = c; 22 | if ((c = getchar()) == '/') { 23 | in_single_comment = 1; 24 | } else if (c == '*') { 25 | in_multi_comment = 1; 26 | } else { 27 | putchar(temp); 28 | } 29 | } 30 | 31 | // Correctly exit single-line comments. 32 | if (in_single_comment) { 33 | if (c == '\n') { 34 | in_single_comment = 0; 35 | } 36 | } 37 | 38 | // Correctly exit multi-line comments by detecting 39 | // termination sequence and not printing it. 40 | if ((c == '*') && in_multi_comment) { 41 | if ((c = getchar()) == '/') { 42 | in_multi_comment = 0; 43 | c = getchar(); 44 | } 45 | } 46 | 47 | if (!(in_single_comment || in_multi_comment)) { 48 | putchar(c); 49 | } 50 | } 51 | return 0; 52 | } 53 | 54 | int main() { return remove_comments(); } 55 | -------------------------------------------------------------------------------- /ch-1/src/1.3-4.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Ex 1.3: Modify the temperature conversion program 5 | * to print a heading above the table. 6 | * Ex 1.4: Write a program to convert celcius 7 | * temps into Fahrenheit. Since C = 5/9 * (F-32), 8 | * F = (C * 9/5) + 32. 9 | */ 10 | 11 | #define LOWER 0.0 // lower limit of temperature table 12 | #define UPPER 300.0 // upper limit 13 | #define STEP 20.0 // size increment 14 | 15 | static int cels_to_fahr() { 16 | double fahr, celsius; 17 | 18 | celsius = LOWER; 19 | printf("C\tF\n"); 20 | printf("-------------\n"); 21 | while (celsius <= UPPER) { 22 | fahr = (9.0 / 5.0 * celsius) + 32.0; 23 | printf("%3.0f %6.1f\n", celsius, fahr); 24 | celsius = celsius + STEP; 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | int main() { return cels_to_fahr(); } 31 | -------------------------------------------------------------------------------- /ch-1/src/1.5.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Ex 1.5: Modify the temperature conversion program to print the table in 5 | * reverse order, that is, from 300 degrees to O. 6 | */ 7 | 8 | static int reverse_table() { 9 | int fahr; 10 | 11 | printf("F\tC\n"); 12 | printf("-------------\n"); 13 | for (fahr = 300; fahr >= 0; fahr = fahr - 20) 14 | printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32)); 15 | return 0; 16 | } 17 | 18 | int main() { return reverse_table(); } 19 | -------------------------------------------------------------------------------- /ch-1/src/1.6.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Ex 1.6: Verify that the expression `getchar() != EOF` is 0 or 1. 4 | 5 | static int verify() { 6 | printf("Enter a character: "); 7 | int c = getchar(); 8 | if (c == EOF) { // getchar == EOF is 1 (or possibly some other nonzero value, 9 | // but `==` should only evaluate to 0 or 1) 10 | printf("getchar() returned EOF.\n"); 11 | return 0; 12 | } else { // getchar == EOF is 0 13 | printf("getchar() didn't return EOF.\n"); 14 | return 0; 15 | } 16 | } 17 | 18 | int main() { return verify(); } 19 | -------------------------------------------------------------------------------- /ch-1/src/1.7.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Ex 1.7: Write a program to print the value of EOF 4 | 5 | int main() { 6 | printf("EOF: %d\n", EOF); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /ch-1/src/1.8.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Ex 1.8: Write a program to count blanks, tabs, and newlines. 4 | 5 | static int count_empties() { 6 | int c, spaces, tabs, newlines; 7 | spaces = tabs = newlines = 0; 8 | 9 | while ((c = getchar()) != EOF) switch (c) { 10 | case ' ': 11 | spaces++; 12 | break; 13 | case '\t': 14 | tabs++; 15 | break; 16 | case '\n': 17 | newlines++; 18 | break; 19 | } 20 | printf("spaces: %d\ntabs: %d\nnewlines: %d\n\n", spaces, tabs, newlines); 21 | return 0; 22 | } 23 | 24 | int main() { return count_empties(); } 25 | -------------------------------------------------------------------------------- /ch-1/src/1.9.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Ex 1.9: Write a program to copy its input to its output, 5 | * replacing each string of one or more blanks with a single blank. 6 | */ 7 | 8 | static int replace_blanks() { 9 | int c = 0, space = 0; 10 | 11 | while ((c = getchar()) != EOF) { 12 | if (c == ' ') { 13 | space = 1; 14 | } else { 15 | if (space == 1) { 16 | putchar(' '); 17 | space = 0; 18 | } 19 | putchar(c); 20 | } 21 | } 22 | putchar('\n'); 23 | return 0; 24 | } 25 | 26 | int main() { return replace_blanks(); } 27 | -------------------------------------------------------------------------------- /ch-2/Makefile: -------------------------------------------------------------------------------- 1 | 2.1: 2.1-build 2.1-basic-test 2 | 3 | 2.2: 2.2-build 4 | ifdef RUN_TESTS 5 | printf "this line is 26 chars long" | $(VALGRIND) ./bin/$@ 6 | printf "" | $(VALGRIND) ./bin/$@ 7 | endif 8 | 9 | 2.3: 2.3-build 2.3-basic-test 10 | 2.4: 2.4-build 2.4-basic-test 11 | 2.5: 2.5-build 2.5-basic-test 12 | 2.6: 2.6-build 2.6-basic-test 13 | 2.7: 2.7-build 2.7-basic-test 14 | 2.8: 2.8-build 2.8-basic-test 15 | 2.9: 2.9-build 2.9-basic-test 16 | 2.10: 2.10-build 2.10-basic-test 17 | -------------------------------------------------------------------------------- /ch-2/include/2.10.h: -------------------------------------------------------------------------------- 1 | char* mylower(const char* const str, const int len); 2 | void test(const char* const str); 3 | -------------------------------------------------------------------------------- /ch-2/include/2.2.h: -------------------------------------------------------------------------------- 1 | int getline_nobool(char* const s, int lim); 2 | -------------------------------------------------------------------------------- /ch-2/include/2.3.h: -------------------------------------------------------------------------------- 1 | int myhtoi(const char* const string, const int len); 2 | void mytest(const char* const numstring); 3 | int hex_to_dec(const char hex); 4 | -------------------------------------------------------------------------------- /ch-2/include/2.4.h: -------------------------------------------------------------------------------- 1 | char* squeeze(char* const s1, const int len1, const char* const s2, 2 | const int len2); 3 | int charmatch(const char c, const char* const str, const int len); 4 | void test(char* const str1, const char* const str2); 5 | -------------------------------------------------------------------------------- /ch-2/include/2.5.h: -------------------------------------------------------------------------------- 1 | int any(const char* const s1, const int len1, const char* const s2, 2 | const int len2); 3 | void test(const char* const s1, const char* const s2); 4 | -------------------------------------------------------------------------------- /ch-2/include/2.6.h: -------------------------------------------------------------------------------- 1 | int set_to_rightmost(int x, const int y, int position, const int n); 2 | int prepare_rightmost(const int y, const int position, const int n, int* mask); 3 | int test(const int x, const int y, const int position, const int count, 4 | const int hypothesis); 5 | -------------------------------------------------------------------------------- /ch-2/include/2.7.h: -------------------------------------------------------------------------------- 1 | int invert(const int x, const int position, const int count); 2 | -------------------------------------------------------------------------------- /ch-2/include/2.8.h: -------------------------------------------------------------------------------- 1 | unsigned int rightrot(unsigned int x, int n); 2 | -------------------------------------------------------------------------------- /ch-2/include/2.9.h: -------------------------------------------------------------------------------- 1 | int bitcount(unsigned x); 2 | -------------------------------------------------------------------------------- /ch-2/src/2.1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | Ex 2.1: Write a program to determine the ranges of char, short, int, 6 | and long variables, both signed and unsigned, by printing appropriate 7 | values from standard headers and by direct computation. Harder if you 8 | compute them: determine the ranges of the various floating-point types. 9 | 10 | --- 11 | Although the C spec determines what the max / min sizes for each type is, this 12 | program is not portable; I am working on an Intel Core i7-6800K CPU, and: 13 | $ arch 14 | x86_64 15 | $ getconf CHAR_BIT 16 | 8 17 | $ getconf WORD_BIT 18 | 32 19 | $ getconf LONG_BIT 20 | 64 21 | */ 22 | 23 | #define WORD_BIT 32 24 | #define LONG_BIT 64 25 | 26 | int main() { 27 | char max_char = 0x7F, min_char = (char)0x80; 28 | unsigned char ucmax = (unsigned char)0xFF; 29 | 30 | printf("Char size: %d, max: %d, min: %d\n", CHAR_BIT, CHAR_MAX, CHAR_MIN); 31 | printf("Max char: %d == 0x%x\n", max_char, max_char); 32 | printf("Min char: %d == 0x%x\n", min_char, min_char); 33 | printf("Max uchar: %u == 0x%x\n\n", ucmax, ucmax); 34 | 35 | short max_shrt = 0x7FFF, min_shrt = (short)0x8000; 36 | unsigned short usmax = (unsigned short)0x7FFF; 37 | 38 | printf("Short size: %d, max: %d, min: %d\n", WORD_BIT / 2, SHRT_MAX, 39 | SHRT_MIN); 40 | printf("Max short: %d == 0x%x\n", max_shrt, max_shrt); 41 | printf("Min short: %d == 0x%x\n", min_shrt, min_shrt); 42 | printf("Max ushort: %u == 0x%x\n\n", usmax, usmax); 43 | 44 | int max_int = 0x7FFFFFFF, min_int = (int)0x80000000; 45 | unsigned int umax = (unsigned int)0xFFFFFFFF; 46 | 47 | printf("Int size: %d, max: %d, min: %d\n", WORD_BIT, INT_MAX, INT_MIN); 48 | printf("Max int: %d == 0x%x\n", max_int, max_int); 49 | printf("Min int: %d == 0x%x\n", min_int, min_int); 50 | printf("Max uint: %u == 0x%x\n\n", umax, umax); 51 | 52 | long max_long = 0x7FFFFFFFFFFFFFFF, min_long = (long)0x8000000000000000; 53 | unsigned long ulmax = (unsigned long)0xFFFFFFFFFFFFFFFF; 54 | 55 | printf("Long size: %d, max: %ld, min: %ld\n", LONG_BIT, LONG_MAX, LONG_MIN); 56 | printf("Max long: %ld == 0x%lx\n", max_long, max_long); 57 | printf("Min long: %ld == 0x%lx\n", min_long, min_long); 58 | printf("Max ulong: %lu == 0x%lx\n", ulmax, ulmax); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /ch-2/src/2.10.c: -------------------------------------------------------------------------------- 1 | #include <2.10.h> 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | Ex 2.10: Rewrite lower() to convert strings of 8 | uppercase letters to lower, but without using if-else. 9 | */ 10 | 11 | int main() { 12 | test("JOSHUA"); 13 | test("bob"); 14 | test(""); 15 | test("YoU ArE a GeNiUs!/#?"); 16 | 17 | return 0; 18 | } 19 | 20 | // mylower(): converts a string to lowercase without if/else 21 | char* mylower(const char* const str, const int len) { 22 | int i; 23 | char* lowered = (char*)malloc((unsigned long)len + 1); 24 | for (i = 0; i < len; i++) { 25 | ((str[i] >= 'A') && (str[i] <= 'Z')) ? (lowered[i] = str[i] + 32) 26 | : (lowered[i] = str[i]); 27 | } 28 | lowered[i] = '\0'; 29 | return lowered; 30 | } 31 | 32 | void test(const char* const str) { 33 | char* lowered = mylower(str, (int)strlen(str)); 34 | printf("%s\n", lowered); 35 | free(lowered); 36 | } 37 | -------------------------------------------------------------------------------- /ch-2/src/2.2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "2.2.h" 5 | #include "common.h" 6 | 7 | /* 8 | Ex 2.2: Write a loop equivalent to the for loop below without using && 9 | or ||. 10 | 11 | for (i=0; i 0) { 21 | printf("%d: %s\n", len, line); 22 | } 23 | 24 | return 0; 25 | } 26 | 27 | // getline(): read a line into s, return length 28 | int getline_nobool(char* const s, const int lim) { 29 | int i; 30 | char c = 0; 31 | 32 | for (i = 0; i < lim - 1; i++) { 33 | c = (char)getchar(); 34 | if (c == EOF) { 35 | break; 36 | } else if (c == '\n') { 37 | break; 38 | } else { 39 | s[i] = c; 40 | } 41 | } 42 | 43 | s[i] = '\0'; 44 | return i; 45 | } 46 | -------------------------------------------------------------------------------- /ch-2/src/2.4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "2.4.h" 6 | 7 | /* 8 | Ex 2.4: Write an alternate version of squeeze(s1 ,s2) that deletes 9 | each character in s1 that matches any character in the string s2. 10 | */ 11 | 12 | int main() { 13 | // o oo ases oo! 14 | test("dog food tastes good!", "dgft"); 15 | 16 | // dg fd tsts gd 17 | test("dog food tastes good!", "oae"); 18 | 19 | // dog food tastes good! 20 | test("dog food tastes good!", "jqx"); 21 | 22 | // dog food tastes good! 23 | test("dog food tastes good!", ""); 24 | 25 | // "" 26 | test("", "dog food tastes good!"); 27 | 28 | return 0; 29 | } 30 | 31 | // squeeze(): delete all chars from s1 that are present in s2; callee 32 | // is responsible for freeing the returned string. 33 | char* squeeze(char* const s1, const int len1, const char* const s2, 34 | const int len2) { 35 | int i, j; 36 | // the squeezed string will never be larger than the original, 37 | // so while possibly wasteful, using len1 is sufficient 38 | char* squeezed = (char*)calloc(1, (unsigned long)len1 + 1); 39 | if (squeezed == NULL) { 40 | return NULL; 41 | } 42 | 43 | for (i = 0, j = 0; i < len1; i++) { 44 | if (charmatch(s1[i], s2, len2) == -1) { 45 | squeezed[j] = s1[i]; 46 | j++; 47 | } 48 | } 49 | 50 | squeezed[i] = '\0'; 51 | return squeezed; 52 | } 53 | 54 | // charmatch(): returns index of c in str1, or -1 if not found. 55 | int charmatch(const char c, const char* const str, const int len) { 56 | int i; 57 | for (i = 0; i < len; i++) { 58 | if (str[i] == c) { 59 | return i; 60 | } 61 | } 62 | return -1; 63 | } 64 | 65 | void test(char* const str1, const char* const str2) { 66 | if (str1 == NULL || str2 == NULL) { 67 | return; 68 | } 69 | 70 | int len1 = (int)strlen(str1); 71 | int len2 = (int)strlen(str2); 72 | char* squeezed = squeeze(str1, len1, str2, len2); 73 | if (squeezed == NULL) { 74 | return; 75 | } 76 | 77 | printf("%s - removing '%s': ", str1, str2); 78 | printf("%s\n", squeezed); 79 | free(squeezed); 80 | } 81 | -------------------------------------------------------------------------------- /ch-2/src/2.5.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "2.5.h" 5 | 6 | /* 7 | Ex 2.5: Write the function any(s1 ,s2), which returns the first location 8 | in the string s1 where any character from the string s2 occurs, or -1 if s1 9 | contains no characters from s2. (The standard library function strpbrk does 10 | the same job but returns a pointer to the location.) 11 | */ 12 | 13 | int main() { 14 | // 1: "o" is found at 4 in string 2 and 1 in string 1 (J != j) 15 | test("Joshua", "Slavoj"); 16 | 17 | // 1: "i" is in dennis, and is at 1 in Nick 18 | test("Nick", "Dennis"); 19 | 20 | // -1 21 | test("King", "Queer"); 22 | 23 | // -1 24 | test("Joshua", ""); 25 | 26 | // -1 27 | test("", "Joshua"); 28 | 29 | return 0; 30 | } 31 | 32 | // any(): find first occurence in s1 of a char in both s1 and s2 33 | int any(const char* const s1, const int len1, const char* const s2, 34 | const int len2) { 35 | int i, j; 36 | for (i = 0; i < len1; i++) { 37 | for (j = 0; j < len2; j++) { 38 | if (s1[i] == s2[j]) { 39 | return i; 40 | } 41 | } 42 | } 43 | 44 | return -1; 45 | } 46 | void test(const char* const s1, const char* const s2) { 47 | const int len1 = (int)strlen(s1); 48 | const int len2 = (int)strlen(s2); 49 | const int i = any(s1, len1, s2, len2); 50 | if (i != -1) { 51 | printf("%c in %s (at %d) and %s.\n", s1[i], s1, i, s2); 52 | } else { 53 | printf("%s and %s share no characters.\n", s1, s2); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ch-2/src/2.7.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "2.7.h" 5 | #include "common.h" 6 | 7 | /* 8 | Ex 2.7: Write a function invert (x,p,n) that returns x with the n bits 9 | that begin at position p inverted (i.e., 1 changed into 0 and vice versa), 10 | leaving the others unchanged. Assuming 11 | p = 1 and n = 3 12 | 13 | 3 2 p 14 | x = 0b 1 0 1 0 15 | becomes 16 | x = 0b 0 1 0 0 17 | --- 18 | Bit flipping can be done via xor - set the bits to flip and leave the other 19 | ones unset: 20 | 0 1 0 1 src 21 | 1 1 1 1 mask 22 | 1 0 1 0 (flipped) 23 | 24 | 0 1 0 1 src 25 | 0 0 0 0 mask 26 | 0 1 0 1 (presered) 27 | 28 | The solution to this problem is: 29 | 1) Create bitmask with bits-to-be-flipped set and others not. 30 | 2) xor with x. 31 | */ 32 | 33 | int main() { 34 | printf("0x%x\n", invert(0xff, 0, 4)); 35 | printf("0x%x\n", invert(0xff, 5, 4)); 36 | printf("0x%x\n", invert(0xff, 3, 4)); 37 | 38 | return 0; 39 | } 40 | 41 | int invert(const int x, const int position, const int count) { 42 | int mask = 0; 43 | create_mask(count, position, &mask, false); 44 | return (x ^ mask); 45 | } 46 | -------------------------------------------------------------------------------- /ch-2/src/2.8.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "2.8.h" 4 | 5 | #pragma clang diagnostic ignored "-Wgnu-binary-literal" 6 | 7 | /* 8 | Ex 2.8: Write a function rightrot(x,n) that returns the value of the 9 | integer x rotated to the right by n bit positions. 10 | */ 11 | 12 | int main() { 13 | // 0b1100...00010100 = c0000014 14 | printf("%x\n", rightrot(0b10100110, 3)); 15 | 16 | // 0b00000111 = 0x7 17 | printf("%x\n", rightrot(0b00111000, 3)); 18 | 19 | // 0x00, no effect. 20 | printf("%x\n", rightrot(0, 3)); 21 | 22 | // 0xFF, no effect 23 | printf("%x\n", rightrot(0xFFFFFFFF, 3)); 24 | 25 | // 0xEFDEADBE 26 | printf("%x\n", rightrot((unsigned)0xDEADBEEF, 8)); 27 | 28 | // 0xEFDEADBE 29 | printf("%x\n", rightrot((unsigned)0xDEADBEEF, 40)); 30 | 31 | return 0; 32 | } 33 | 34 | // rightrot(): rotate x to the right n places, with wrapping. 35 | // rightrot() has to use unsigned ints so that C does not 36 | // try to preserve the sign bit if we shift a number with it set. 37 | unsigned int rightrot(unsigned int x, int n) { 38 | // Rotating 33 places is the same as rotating 1 for a 32bit word. 39 | 40 | if (n >= 32) { 41 | n = n % 32; 42 | } 43 | 44 | // capture n lowest bits 45 | unsigned lowest_mask = (unsigned)~((unsigned)(~0) << n); 46 | unsigned int lowest = x & lowest_mask; 47 | 48 | // shift x by n to the right 49 | x = x >> n; 50 | 51 | // take n lowest bits and make them n highest bits 52 | unsigned int highest = lowest << (32 - n); 53 | 54 | // combine 55 | return highest | x; 56 | } 57 | -------------------------------------------------------------------------------- /ch-2/src/2.9.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "2.9.h" 4 | 5 | /* 6 | Ex 2.9: In a two's complement number system, x &= (x-1) deletes the 7 | rightmost 1-bit in x. Explain why. Use this observation to write a faster 8 | version of bitcount. 9 | */ 10 | 11 | int main() { 12 | /* 13 | For any number x, 14 | x = 0b...1 15 | x-1 = 0b...0 16 | or vice versa; there's no 17 | number where its previous value 18 | and itself share the rightmost bit. 19 | */ 20 | 21 | printf("%d\n", bitcount(0xF013)); // runs in 7 steps instead of 16. 22 | return 0; 23 | } 24 | 25 | int bitcount(unsigned x) { 26 | int b = 0; 27 | 28 | while (x != 0) { 29 | /* 30 | On every iteration, the value x and x-1 31 | differ in the 0th bit (starting at the right). 32 | Using x &= x-1, we can "decrement", and capture all 33 | bits in b steps, where b is equal to the number of bits. 34 | This is better than the original bitcount() because 35 | it ran in n time for n-bit strings every time. 36 | I had to look at the answer manual to figure 37 | this one out. ;) 38 | */ 39 | x &= x - 1; 40 | b++; 41 | } 42 | return b; 43 | } 44 | -------------------------------------------------------------------------------- /ch-3/Makefile: -------------------------------------------------------------------------------- 1 | 3.1: 3.1-build 3.1-basic-test 2 | 3.2: 3.2-build 3.2-basic-test 3 | 3.3: 3.3-build 3.3-basic-test 4 | 3.4: 3.4-build 3.4-basic-test 5 | 3.5: 3.5-build 3.5-basic-test 6 | 3.6: 3.6-build 3.6-basic-test 7 | -------------------------------------------------------------------------------- /ch-3/include/3.1.h: -------------------------------------------------------------------------------- 1 | int binsearch(const int x, const int* const v, const int n); 2 | int newbinsearch(const int x, const int* const v, const int n); 3 | void test(const int val, const int* const v, const int n); 4 | -------------------------------------------------------------------------------- /ch-3/include/3.2.h: -------------------------------------------------------------------------------- 1 | char* escape(const char* const str, const int t_len); 2 | char* unescape(const char* const str, const int t_len); 3 | void test(const char* const str); 4 | -------------------------------------------------------------------------------- /ch-3/include/3.3.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | char* expand(const char* const str, const int len); 4 | bool valid_expansion(const char* const str, const int i, const int len); 5 | char* generate_expansion(const char a, const char b); 6 | int get_char_type(const char a); 7 | int is_upper(const char a); 8 | int is_lower(const char a); 9 | int is_int(const char a); 10 | -------------------------------------------------------------------------------- /ch-3/src/3.1.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "3.1.h" 4 | 5 | /* 6 | Ex 3.1: Our binary search makes two tests inside the loop, when one 7 | would suffice (at the price of more tests outside). Write a version with only 8 | one test inside the loop and measure the difference in run-time. 9 | */ 10 | 11 | int main() { 12 | int ints[] = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096}; 13 | test(32, ints, 14); 14 | test(512, ints, 14); 15 | test(2555, ints, 14); 16 | 17 | return 0; 18 | } 19 | 20 | int binsearch(const int x, const int* const v, const int n) { 21 | int high, low, mid; 22 | 23 | low = 0; 24 | high = n - 1; 25 | while (low <= high) { 26 | mid = (low + high) / 2; 27 | if (x < v[mid]) { 28 | high = mid - 1; 29 | } else if (x > v[mid]) { 30 | low = mid + 1; 31 | } else // found match 32 | { 33 | return mid; 34 | } 35 | } 36 | return -1; // no match 37 | } 38 | 39 | // This is not asypmtotically faster than the other bin search; 40 | // it still takes log(n) steps instead of 3*log(n) steps for an 41 | // array of n integers - both are O(log(n)); 42 | int newbinsearch(const int x, const int* const v, const int n) { 43 | int high, low, mid = 0; 44 | 45 | low = 0; 46 | high = n - 1; 47 | while (low <= high && x != v[mid]) { 48 | mid = (low + high) / 2; 49 | (x < v[mid]) ? (high = mid - 1) : (low = mid + 1); 50 | } 51 | if (x == v[mid]) { 52 | return mid; 53 | } 54 | return -1; // no match 55 | } 56 | 57 | void test(const int val, const int* const v, const int n) { 58 | printf("binsearch: %d at %d\n", val, binsearch(val, v, n)); 59 | printf("newbinsearch: %d at %d\n", val, newbinsearch(val, v, n)); 60 | } 61 | -------------------------------------------------------------------------------- /ch-3/src/3.4.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | 5 | /* 6 | In a two's complement number representation, our version of itoa() 7 | does not handle the largest negative number, that is, the value of n equal 8 | to -(2^wordsize-1). Explain why not. Modify it to print that value correctly, 9 | regardless of the machine on which it runs. 10 | 11 | In a two's compliment system, the -(2^wordsize-1) value presents a unique 12 | edge case, as this value this number multiplied by -1 is itself. For 32bit 13 | wordsize, 0x80000000 is the largest and "first" negative number (if we start 14 | at zero and add 1 iteratively until we hit the first negative number). In 15 | itoa(), our do-while loop runs while the parameter n > 0; however passing in 16 | -2147483648 is represented as 0x80000000, which is our edge case. So our 17 | strategy to ensure we never execute our do-while loop on a negative number 18 | fails. Other negative numbers don't have this issue - multiplying them 19 | by -1 produces a positive value. 20 | 21 | */ 22 | 23 | static void itoa(const int n, char* const s) { 24 | int i, is_negative; 25 | unsigned val; 26 | i = 0; 27 | 28 | // If the value is negative, multiply by 29 | // -1 and store the result as an unsigned int. 30 | is_negative = (n < 0); 31 | is_negative ? (val = (unsigned)(n * -1)) : (val = (unsigned)n); 32 | 33 | do { 34 | s[i++] = val % 10 + '0'; 35 | } while ((val /= 10) > 0); 36 | if (is_negative) { 37 | s[i++] = '-'; 38 | } 39 | s[i] = '\0'; 40 | reverse(s, i); 41 | } 42 | 43 | int main() { 44 | char numstring0[MAXLEN]; 45 | int num0 = 0; // 0 46 | itoa(num0, numstring0); 47 | printf("%d == %s\n", num0, numstring0); 48 | 49 | char numstring1[MAXLEN]; 50 | int num1 = 0x7FFFFFFF; // 2147483647 51 | itoa(num1, numstring1); 52 | printf("%d == %s\n", num1, numstring1); 53 | 54 | char numstring2[MAXLEN]; 55 | int num2 = (int)0x80000000; // -2147483648 56 | itoa(num2, numstring2); 57 | printf("%d == %s\n", num2, numstring2); 58 | 59 | char numstring3[MAXLEN]; 60 | int num3 = (int)0xFFFFFFFF; // -1 61 | itoa(num3, numstring3); 62 | printf("%d == %s\n", num3, numstring3); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /ch-3/src/3.5.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | 5 | /* 6 | Write the function itob(n,s,b) that converts the integer n 7 | into a base b character representation in the string s. In particular, 8 | itob(n,s, 16) formats n as a hexadecimal integer in s. 9 | 10 | For the purposes of this, I am only implementing base 0 through base 36 to avoid 11 | having to pick characters to represent 36+. 12 | */ 13 | 14 | static void itob(const int n, unsigned char* const s, const unsigned b) { 15 | int i; 16 | unsigned val, remainder; 17 | i = 0; 18 | 19 | // Ignore unsuported inputs 20 | if ((b < 2) || (b > 36)) { 21 | printf("Bases 0, 1, and 36+ not supported.\n"); 22 | return; 23 | } 24 | 25 | // Fix 0x80000000 issue; if the value is negative, multiply by 26 | // -1 and store the result as an unsigned int. 27 | (n < 0) ? (val = (unsigned)(n * -1)) : (val = (unsigned)(n)); 28 | 29 | do { 30 | remainder = (unsigned)(val % b); 31 | if (remainder >= 10) { 32 | // If we go above base 10, start using 33 | // A through Z; offset is different, and 34 | // we need to start from 0 (subtract 10). 35 | s[i] = (unsigned char)('A' + remainder - 10); 36 | } else { 37 | // same logic as itoa() 38 | s[i] = (unsigned char)('0' + remainder); 39 | } 40 | i++; 41 | } while ((val /= b) > 0); 42 | 43 | if (n < 0) { 44 | s[i++] = '-'; 45 | } 46 | s[i] = '\0'; 47 | reverse((char*)s, i); 48 | } 49 | 50 | int main() { 51 | // 1010 52 | unsigned char numstring[MAXLEN]; 53 | int num = 10; 54 | itob(num, numstring, 2); 55 | printf("%d base 10 == %s base %d\n", num, numstring, 2); 56 | 57 | // Z 58 | num = 35; 59 | itob(num, numstring, 36); 60 | printf("%d base 10 == %s base %d\n", num, numstring, 36); 61 | 62 | // Quits with error, should print "35 base 10 == Z base 1" 63 | // since values are unchanged from previous iteration 64 | num = 35; 65 | itob(num, numstring, 1); 66 | printf("%d base 10 == %s base %d\n", num, numstring, 1); 67 | 68 | // 0 69 | num = 0; 70 | itob(num, numstring, 10); 71 | printf("%d base 10 == %s base %d\n", num, numstring, 10); 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /ch-3/src/3.6.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | 5 | #define MAX_LEN 1000 6 | 7 | /* 8 | Write a version of itoa that accepts three arguments instead of 9 | two. The third argument is a minimum field width; the converted number must 10 | be padded with blanks on the left if necessary to make it wide enough. 11 | */ 12 | 13 | static void itoa(const int n, char* const s, const int pad) { 14 | int i = 0; 15 | unsigned val; 16 | 17 | // If the value is negative, multiply by 18 | // -1 and store the result as an unsigned int. 19 | (n < 0) ? (val = (unsigned int)(n * -1)) : (val = (unsigned int)n); 20 | 21 | do { 22 | s[i++] = val % 10 + '0'; 23 | } while ((val /= 10) > 0); 24 | if (n < 0) { 25 | s[i++] = '-'; 26 | } 27 | 28 | // Doesn't execute if we exceed padding 29 | while (i < pad) { 30 | s[i++] = ' '; 31 | } 32 | s[i] = '\0'; 33 | reverse(s, i); 34 | } 35 | 36 | int main() { 37 | char numstring[MAX_LEN]; 38 | int num = 0; // 0 39 | itoa(num, numstring, 9); 40 | printf("%s == %d\n", numstring, num); 41 | 42 | num = 10; // 0 43 | itoa(num, numstring, 9); 44 | printf("%s == %d\n", numstring, num); 45 | 46 | num = 1000000; // 0 47 | itoa(num, numstring, 9); 48 | printf("%s == %d\n", numstring, num); 49 | 50 | num = 1000000000; // 0 51 | itoa(num, numstring, 9); 52 | printf("%s == %d\n", numstring, num); 53 | 54 | num = 0x7FFFFFFF; // 0 55 | itoa(num, numstring, 9); 56 | printf("%s == %d\n", numstring, num); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /ch-4/Makefile: -------------------------------------------------------------------------------- 1 | 4.1: 4.1-build 4.1-basic-test 2 | 3 | 4.2: 4.2-build 4 | ifdef RUN_TESTS 5 | printf "10.0e4" | $(VALGRIND) ./bin/$@ 6 | printf "10.0e-4" | $(VALGRIND) ./bin/$@ 7 | echo -10.0e4 | $(VALGRIND) ./bin/$@ 8 | echo -10.0e-4 | $(VALGRIND) ./bin/$@ 9 | printf "" | $(VALGRIND) ./bin/$@ 10 | endif 11 | 12 | # the reverse polish calc is exercises 4.3 through 4.10. 13 | rpc: clean 14 | $(COMPILE) -I ch-4/rpc/include/ ch-4/rpc/src/*.c -o bin/$@ 15 | ifdef RUN_TESTS 16 | printf "4 5 +" | $(VALGRIND) ./bin/$@ | grep "9" 17 | printf "4 5 + 5 6 + -\n" | $(VALGRIND) ./bin/$@ | grep "\-2" 18 | printf "5 SIN" | $(VALGRIND) ./bin/$@ | grep "\-0.95892427" 19 | printf "b 6 = \n b 10 *\n" | $(VALGRIND) ./bin/$@ | grep "60" 20 | printf "" | $(VALGRIND) ./bin/$@ 21 | endif 22 | 23 | 4.11: 4.11-build 24 | ifdef RUN_TESTS 25 | printf "20 5 +\n" | $(VALGRIND) ./bin/$@ 26 | printf "20 5 5 + +\n" | $(VALGRIND) ./bin/$@ 27 | printf "" | $(VALGRIND) ./bin/$@ 28 | endif 29 | 30 | 4.12-13: 4.12-13-build 4.12-13-basic-test 31 | 4.14: 4.14-build 4.14-basic-test 32 | -------------------------------------------------------------------------------- /ch-4/README.md: -------------------------------------------------------------------------------- 1 | # Ch. 4 2 | To avoid unnecessary code replication over minor changes, exercises 4.3 through 4.9 have been grouped into `reverse-polish-calc/`, which I completely overhauled to use more robust parsing / lexing. 3 | 4 | ## Notes about the exercises 5 | * 4.9: 6 | * K&R claim that `getch()` / `ungetch()` don't handle `EOF` correctly, but the expected behavior occurs both in my implementation and theirs so I'm not quite sure what this question is asking. `EOF` does cause the program to exit correctly, although the `EOF` character gets pushed back into the array by `ungets()`. 7 | * 4.10: 8 | * I sort-of skipped this because I re-architected the RPN calculator to loosely couple and more robustly handle parsing, lexing, and token handling; a solution involving parsing the entire string either would require 1) tightly-recoupling these things so that in one loop you can handle the entire string, determining symbols and pushing it to the stack, or 2) having an intermediary data structure similar to the ungetch() buffer. 9 | -------------------------------------------------------------------------------- /ch-4/rpc/README.md: -------------------------------------------------------------------------------- 1 | # Reverse Polish Notation (RPN) calculator 2 | To avoid unnecessary code replication over minor changes, exercises 4.3 through 4.9 have been grouped into a single application here, which I completely overhauled to use more robust parsing / lexing. 3 | 4 | ## Use 5 | Valid input is of the form ` `. Expressions 6 | may either be floating point values, variables, or a nested expression. 7 | 8 | ## Supported features 9 | * You can assign single-character variables via the `=` operator: `a 4 =`; assignment is C-style (i.e. returns the value assigned). 10 | * Basic arithmetic operators: `+`, `-`, `*`, `/`, and `%`. 11 | * Math functions as operators, which are 3 or 4 letters long: 12 | * SIN 13 | * COS 14 | * TAN 15 | * ASIN 16 | * ACOS 17 | * ATAN 18 | * POW 19 | * EXP 20 | * SQRT 21 | * FLOR 22 | 23 | ## Building 24 | `make calc` 25 | 26 | ## Example use 27 | ``` 28 | 4 5 + 29 | 9 30 | 4 5 + 5 6 + - 31 | -2 32 | a 5 = 33 | Stored a = 5.000000. 34 | 5 35 | b 6 = 36 | Stored b = 6.000000. 37 | 6 38 | b 10 * SIN 39 | -0.30481062 40 | ``` -------------------------------------------------------------------------------- /ch-4/rpc/src/stack.c: -------------------------------------------------------------------------------- 1 | #include "rpc.h" 2 | 3 | static int sp = -1; 4 | static operand stack[MAX_STACK_SIZE]; 5 | 6 | // push() - push f onto value stack 7 | int push(const operand op) { 8 | if (sp < MAX_STACK_SIZE) { 9 | stack[++sp] = op; 10 | return 0; 11 | } else { 12 | printf("Error; stack full.\n"); 13 | return -1; 14 | } 15 | } 16 | 17 | // pop: return the top value/type pair from stack and decrement the stack 18 | // pointer 19 | operand pop(void) { 20 | if (sp == -1) { 21 | printf("Error: stack empty.\n"); 22 | return (operand){GARBAGE, 0, 0.0}; 23 | } else if (sp < -1) { 24 | printf("Error: stack invalid.\n"); 25 | return (operand){GARBAGE, 0, 0.0}; 26 | } else { 27 | return stack[sp--]; 28 | } 29 | } 30 | 31 | // peek: return the top value/type pair from stack without decrementing sp; 32 | operand peek(void) { 33 | if (sp == -1) { 34 | printf("Error: stack empty.\n"); 35 | return (operand){GARBAGE, 0, 0.0}; 36 | } else if (sp < -1) { 37 | printf("Error: stack invalid.\n"); 38 | return (operand){GARBAGE, 0, 0.0}; 39 | } else { 40 | return stack[sp]; 41 | } 42 | } 43 | 44 | // duplicate_top: instructions were unclear, but assuming that 45 | // this means add another element equal to the top element. 46 | void duplicate_top() { 47 | operand top = pop(); 48 | push(top); 49 | push(top); 50 | } 51 | 52 | // swap_top: switch the top two elements of the stack 53 | void swap_top() { 54 | // zero-initialize for -Werror=maybe-uninitialized 55 | operand top = pop(); 56 | operand second = pop(); 57 | 58 | push(top); 59 | push(second); 60 | } 61 | 62 | int get_stack_top() { return sp; } 63 | -------------------------------------------------------------------------------- /ch-4/rpc/src/vars.c: -------------------------------------------------------------------------------- 1 | #include "rpc.h" 2 | 3 | // the vars table stores variables for 4 | // a-z at offsets 0-25, and then 5 | // A-Z at offsets 26-51 6 | static double vars_table[MAX_STACK_SIZE]; 7 | 8 | void assign(const operand val1, const operand val2) { 9 | if (!validate_var(val1)) { 10 | return; 11 | } 12 | 13 | const operand val2_ready = (val2.type == VAR) ? dereference(val2) : val2; 14 | 15 | if (val1.cvalue >= 'a' && val1.cvalue <= 'z') { 16 | vars_table[val1.cvalue - 'a'] = val2_ready.dvalue; 17 | } else if (val1.cvalue >= 'A' && val1.cvalue <= 'Z') { 18 | // upper case var storage starts at vars_table[26] 19 | vars_table[val1.cvalue - 'A' + 26] = val2_ready.dvalue; 20 | } 21 | 22 | // C style assignment returns the value assigned 23 | push(val2_ready); 24 | } 25 | 26 | operand dereference(const operand op) { 27 | if (!validate_var(op)) { 28 | return (operand){GARBAGE, 0, 0.0}; 29 | } 30 | 31 | operand new; 32 | if (op.cvalue >= 'a' && op.cvalue <= 'z') { 33 | new.dvalue = vars_table[op.cvalue - 'a']; 34 | } else if (op.cvalue >= 'A' && op.cvalue <= 'Z') { 35 | new.dvalue = vars_table[op.cvalue - 'A' + 26]; 36 | } else { 37 | printf("Error: Invalid lvalue for variable assignment.\n"); 38 | return (operand){GARBAGE, 0, 0.0}; 39 | } 40 | 41 | new.type = VAL; 42 | new.cvalue = 0; 43 | return new; 44 | } 45 | 46 | int validate_var(const operand var) { 47 | if (var.type != VAR) { 48 | printf("validate_var() | Error: operand is not a VAR.\n"); 49 | return 0; 50 | } 51 | 52 | if (!((var.cvalue >= 'a' && var.cvalue <= 'z') || 53 | (var.cvalue >= 'A' && var.cvalue <= 'Z'))) { 54 | printf("validate_var() | Error: invalid variable name %c.\n", var.cvalue); 55 | return 0; 56 | } 57 | 58 | return 1; 59 | } 60 | -------------------------------------------------------------------------------- /ch-4/src/4.1.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Ex. 4-1: Write the function strrindex(s,t) , which returns the position of 4 | // the rightmost occurrence of t in s , or -1 if there is none. 5 | 6 | static char pattern[] = {"ould"}; 7 | 8 | // strindex: return the rightmost index of t in s, -1 if not found. 9 | // start at the right and go left until either the beginning of the string 10 | // occurs or a match is found 11 | static int strrindex(const char* const s, const int s_len, 12 | const char* const t) { 13 | int i, j, k; 14 | 15 | for (i = s_len - 1; i > 0; i--) { 16 | for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++) { 17 | // noop 18 | } 19 | if ((k > 0) && (t[k] == '\0')) { 20 | return i; 21 | } 22 | } 23 | return -1; 24 | } 25 | 26 | // Find all lines matching pattern 27 | int main() { 28 | // 9 29 | char good1[] = "goodfoodwouldmold"; 30 | printf("%d\n", strrindex(good1, 17, pattern)); 31 | 32 | // 11 33 | char good2[] = "wouldwouldwould"; 34 | printf("%d\n", strrindex(good2, 15, pattern)); 35 | 36 | // -1 37 | char good3[] = "g0000000000000d"; 38 | printf("%d\n", strrindex(good3, 15, pattern)); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /ch-4/src/4.14.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | Define a macro swap(t,x,y) that interchanges two arguments of type t. I'm going 5 | to assume for this exercise that you would invoke it like this: int len = 0; int 6 | index = 1; swap(int, len, index); 7 | 8 | After which len would equal 1 and index would equal 2. 9 | */ 10 | 11 | #define SWAP(t, x, y) \ 12 | t tmp = x; \ 13 | x = y; \ 14 | y = tmp; 15 | 16 | int main() { 17 | int one = 1; 18 | int two = 2; 19 | printf("one: %d\ntwo: %d\n", one, two); 20 | SWAP(int, one, two); 21 | printf("one: %d\ntwo: %d\n", one, two); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /ch-4/src/4.2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | 7 | /* 8 | * Ex. 4.2: Extend atof to handle scientific notation of the form 123.45e-6 9 | * where a floating-point number may be followed by e or E and an 10 | * optionally signed exponent. 11 | */ 12 | 13 | // myatof() - convert string s to double 14 | static double myatof(const char* const s) { 15 | char sn_exp[MAXLEN]; 16 | double val, power; 17 | int i, j, sign, sn_sign, sn_val; 18 | 19 | for (i = 0; isspace(s[i]); i++) { 20 | } // no-op, skip whitespace 21 | 22 | sign = (s[i] == '-') ? -1 : 1; 23 | 24 | if (s[i] == '+' || s[i] == '-') { 25 | i++; 26 | } 27 | 28 | for (val = 0.0; isdigit(s[i]); i++) { 29 | val = 10.0 * val + (s[i] - '0'); 30 | } 31 | 32 | if (s[i] == '.') { 33 | i++; 34 | } 35 | 36 | for (power = 1.0; isdigit(s[i]); i++) { 37 | val = 10.0 * val + (s[i] - '0'); 38 | power *= 10.0; 39 | } 40 | 41 | // call atoi() on the remaining values in the string, 42 | // and combine result to our power converstion depending 43 | // on its sign. 44 | if (s[i] == 'e' || s[i] == 'E') { 45 | i++; 46 | if (s[i] == '-') { 47 | sn_sign = 1; 48 | i++; 49 | } else { 50 | sn_sign = 0; 51 | } 52 | for (j = 0; isdigit(s[i]); i++, j++) { 53 | sn_exp[j] = s[i]; 54 | } 55 | sn_exp[j] = '\0'; 56 | 57 | sn_val = atoi(sn_exp); 58 | 59 | // Normally for scientific notation, you 60 | // multiply for positive exponents, but 61 | // we are dividing in the return statement 62 | // of atof(), so multiplying power by 10 63 | // here makes the returned value smaller. 64 | if (sn_sign) { 65 | while (sn_val > 0) { 66 | power *= 10.0; 67 | sn_val--; 68 | } 69 | } else { 70 | while (sn_val > 0) { 71 | power /= 10.0; 72 | sn_val--; 73 | } 74 | } 75 | } 76 | 77 | return sign * (val / power); 78 | } 79 | 80 | int main() { 81 | char line[MAXLEN]; 82 | 83 | while (mygetline(line, MAXLEN) > 0) { 84 | printf("%f\n", myatof(line)); 85 | } 86 | 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /ch-5/Makefile: -------------------------------------------------------------------------------- 1 | 5.1: 5.1-build 2 | ifdef RUN_TESTS 3 | echo "-55" | $(VALGRIND) ./bin/$@ 4 | -echo "-" | $(VALGRIND) ./bin/$@ # This causes a false positive in Valgrind; see README 5 | endif 6 | 7 | 5.2: 5.2-build 8 | ifdef RUN_TESTS 9 | printf "4.5" | $(VALGRIND) ./bin/$@ 10 | echo "-12.5" | $(VALGRIND) ./bin/$@ 11 | printf ".5" | $(VALGRIND) ./bin/$@ 12 | printf "0.5" | $(VALGRIND) ./bin/$@ 13 | printf "2." | $(VALGRIND) ./bin/$@ 14 | -echo "-" | $(VALGRIND) ./bin/$@ # This causes a false positive in Valgrind; see README 15 | endif 16 | 17 | 5.3: 5.3-build 5.3-basic-test 18 | 5.4: 5.4-build 5.4-basic-test 19 | 5.5: 5.5-build 5.5-basic-test 20 | 5.6: 5.6-build 5.6-basic-test 21 | 22 | 5.7: 5.7-build 23 | ifdef RUN_TESTS 24 | printf "A line\nQ line\nD line\nC line\nS line\n" | $(VALGRIND) ./bin/$@ 25 | printf "" | $(VALGRIND) ./bin/$@ 26 | endif 27 | 28 | 5.8-9: 5.8-9-build 5.8-9-basic-test 29 | 5.10: 5.10-build 30 | ifdef RUN_TESTS 31 | $(VALGRIND) ./bin/$@ 10 5 + 32 | $(VALGRIND) ./bin/$@ 10 5 / 33 | $(VALGRIND) ./bin/$@ 5 10 - 34 | $(VALGRIND) ./bin/$@ 5 10 "*" 35 | @# ((10 5 +) + (10 / 5)) + ((5 10 -) + (5 10 *)) 36 | $(VALGRIND) ./bin/$@ 10 5 + 10 5 / + 5 10 - 5 10 "*" + + 37 | @# tests that should cause exceptions 38 | -$(VALGRIND) ./bin/$@ 10 5 % 39 | -$(VALGRIND) ./bin/$@ herp derp ferp 40 | -$(VALGRIND) ./bin/$@ 41 | endif 42 | 43 | # tabber covers the entab / detab exercises in 5.11 and 5.12 44 | # tail is exercise 5.13 45 | tabber tail: clean 46 | $(COMPILE) -I ch-5/$@/include ch-5/$@/src/*.c -o bin/$@ 47 | ifdef RUN_TESTS 48 | ./ch-5/$@/$@-test.sh 49 | endif 50 | 51 | # sort is exercises 5.14 through 5.17 52 | sort: clean 53 | $(COMPILE) -I ch-5/sort/include/ ch-5/sort/src/*.c -o bin/$@ 54 | ifdef RUN_TESTS 55 | cat ch-5/sort/sort-test.txt | $(VALGRIND) ./bin/$@ 56 | cat ch-5/sort/sort-test-mixed-case.txt | $(VALGRIND) ./bin/$@ -f 57 | cat ch-5/sort/sort-test-homedir.txt | $(VALGRIND) ./bin/$@ -d -r 58 | cat ch-5/sort/sort-test-subfields.txt | $(VALGRIND) ./bin/$@ -r -i -n 59 | endif 60 | 61 | # decl/undecl are exercises 5.18 through 5.20 62 | decl undecl: clean 63 | $(COMPILE) -I ch-5/decl/include/ ch-5/decl/src/common/*.c ch-5/decl/src/$@-main.c -o bin/$@ 64 | ./ch-5/decl/$@-test.sh 65 | -------------------------------------------------------------------------------- /ch-5/README.md: -------------------------------------------------------------------------------- 1 | Ch-5 2 | --- 3 | 4 | ## Notes about the exercises 5 | * `5.1` and `5.2` 6 | * Calling `ungetc(c, stdin)` before a function exits appears to cause a false positive in Valgrind for that character being lost (even though the character is read back by the caller if the function returns -1) 7 | * `5.2` 8 | * I wrote `getdouble()` instead. 9 | * `decl` 10 | * I rewrote `decl` such that it does not run in a loop, and only accepts one line of input, so `recovering from errors` isn't really necessary anymore - instead, I added checks to ensure the program terminates immediately if erroneous input is detected. 11 | * `scan-build` throws an error about `TYPE` being unused in `dirdecl()`, but this is acceptable given that it is a global variable. 12 | -------------------------------------------------------------------------------- /ch-5/decl/README.md: -------------------------------------------------------------------------------- 1 | decl 2 | --- 3 | 4 | ## Grammar: 5 | ``` 6 | decl -> (optional *'s) direct-decl 7 | direct-decl -> name 8 | -> (decl) 9 | -> direct-decl(typed-expr) 10 | -> direct-decl[optional size] 11 | typed-expr -> type name 12 | type -> (optional const) (char | short | int | long | float | double | void) (optional *) 13 | name -> (optional const) any string of alphanum chars, not reserved 14 | 15 | ``` 16 | 17 | ## Assumptions 18 | - we will not handle compound types, e.g. `unsigned long` 19 | - we will not handle structs 20 | - function arguments will only be basic types: 21 | - allowed: 22 | - `int x(int a, int b)` 23 | - `int x(const int a)` 24 | - not allowed: 25 | - `int x(int (*f)(int, int), int a)` 26 | 27 | 28 | ## Directions 29 | ``` 30 | Exercise 5-18. Make decl recover from input errors. 31 | 32 | Exercise 5-19. Modify undecl so that it does not add redundant parentheses to declarations. 33 | 34 | Exercise5-20. Expand decl to handle declarations with function argument types, qualifiers like const, and so on. 35 | ``` 36 | 37 | ## Notes 38 | const int x; 39 | decl 40 | const direct-decl 41 | 42 | 43 | 44 | const char* const x; 45 | x(const int y); 46 | 47 | const char* const x(const char* const y); 48 | -------------------------------------------------------------------------------- /ch-5/decl/decl-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PATH=`pwd` 3 | while read declline 4 | do 5 | CDECL_OUT=`/usr/bin/cdecl explain $declline` 6 | DECL_OUT=`$PATH/bin/decl <<< $declline` 7 | printf "$declline\ncdecl: $CDECL_OUT\ndecl: $DECL_OUT\n\n" 8 | done < ch-5/decl/decl-test.txt 9 | -------------------------------------------------------------------------------- /ch-5/decl/decl-test.txt: -------------------------------------------------------------------------------- 1 | char* a(int) 2 | int b(const char) 3 | int c(int) 4 | int d(int, int) 5 | char* e(const char) 6 | int f(const int) 7 | char* g(const char*) 8 | const int h 9 | char **i 10 | int (*j)[13] 11 | int *k[13] 12 | void *l() 13 | void (*m)() 14 | char (*(*n())[])() 15 | char (*(*o[3])())[5] 16 | -------------------------------------------------------------------------------- /ch-5/decl/include/decl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define MAXTOKEN 100 4 | 5 | // globals 6 | enum { NAME, PARENS, BRACKETS }; 7 | extern int tokentype; 8 | extern char token[]; 9 | extern char name[]; 10 | extern char datatype[]; 11 | extern char out[]; 12 | 13 | #define TYPESCOUNT 7 14 | extern char* types[]; 15 | 16 | // decl 17 | int decl(void); 18 | 19 | // dirdecl 20 | int dirdecl(void); 21 | 22 | // gettoken 23 | int gettoken(void); 24 | int gettype(void); 25 | int istype(void); 26 | -------------------------------------------------------------------------------- /ch-5/decl/src/common/decl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "decl.h" 5 | 6 | // decl(): parse a declarator 7 | int decl(void) { 8 | int ns; 9 | 10 | for (ns = 0; gettoken() == '*';) { 11 | ns++; 12 | } 13 | if (dirdecl() == 0) { 14 | while (ns-- > 0) { 15 | strcat(out, " pointer to"); 16 | } 17 | return 0; 18 | } else { 19 | return -1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ch-5/decl/src/common/dirdecl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "decl.h" 5 | 6 | // dirdecl: parse a direct declarator via the grammar, possibly recursing into 7 | // decl if necessary. 8 | int dirdecl(void) { 9 | int type = 0; 10 | 11 | if (tokentype == '(') { 12 | if (decl() == -1) { 13 | return -1; 14 | } 15 | if (tokentype != ')') { 16 | printf("error: missing )\n"); 17 | return -1; 18 | } 19 | } else if (tokentype == NAME) { 20 | strcpy(name, token); 21 | } else { 22 | printf("error: expected name or (decl)\n"); 23 | return -1; 24 | } 25 | 26 | while ((type = gettoken()) == PARENS || type == '(' || type == BRACKETS) { 27 | switch (type) { 28 | case PARENS: 29 | strcat(out, " function returning"); 30 | break; 31 | case BRACKETS: 32 | strcat(out, " array"); 33 | strcat(out, token); 34 | strcat(out, " of"); 35 | break; 36 | case '(': 37 | strcat(out, " function ("); 38 | char args[] = {" "}; 39 | while ((args[0] = (char)getchar()) != ')') { 40 | strcat(out, args); 41 | } 42 | // type = PARENS; 43 | strcpy(token, "()"); 44 | strcat(out, ") returning"); 45 | break; 46 | } 47 | } 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /ch-5/decl/src/common/gettoken.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "decl.h" 6 | 7 | int gettoken(void) { 8 | int c; 9 | char *p = token; 10 | 11 | while ((c = getchar()) == ' ' || c == '\t') { 12 | // skip whitespaces 13 | } 14 | 15 | if (c == '(') { 16 | if ((c = getchar()) == ')') { 17 | strcpy(token, "()"); 18 | // printf("gettoken() | got (), tokentype PARENS\n"); 19 | return tokentype = PARENS; 20 | } else { 21 | ungetc(c, stdin); 22 | // printf("gettoken() | got (, tokentype (\n"); 23 | return tokentype = '('; 24 | } 25 | } else if (c == '[') { 26 | for (*p++ = (char)c; (*p++ = (char)getchar()) != ']';) { 27 | // skip array size specifier 28 | } 29 | *p = '\0'; 30 | // printf("gettoken() | got [], tokentype BRACKETS\n"); 31 | return tokentype = BRACKETS; 32 | } else if (isalpha(c)) { 33 | for (*p++ = (char)c; isalnum(c = getchar());) { 34 | *p++ = (char)c; 35 | } 36 | *p = '\0'; 37 | ungetc(c, stdin); 38 | // printf("gettoken() | got %s, tokentype NAME\n", token); 39 | return tokentype = NAME; 40 | } else { 41 | // printf("gettoken() | got %c, tokentype %c\n", c, c); 42 | return tokentype = c; 43 | } 44 | } 45 | 46 | int gettype(void) { 47 | datatype[0] = '\0'; 48 | gettoken(); 49 | if (strcmp(token, "const") == 0) { 50 | strcat(datatype, token); 51 | strcat(datatype, " "); 52 | gettoken(); 53 | } 54 | if (istype() != -1) { 55 | strcat(datatype, token); 56 | strcat(datatype, " "); 57 | } else { 58 | printf("decl: error - %s is not a type, quitting.\n", token); 59 | return -1; 60 | } 61 | return 0; 62 | } 63 | 64 | int istype(void) { 65 | for (int i = 0; i < TYPESCOUNT; i++) { 66 | if (strcmp(types[i], token) == 0) { 67 | // printf("istype() | %s is a type.\n", token); 68 | return i; 69 | } 70 | } 71 | return -1; 72 | } 73 | -------------------------------------------------------------------------------- /ch-5/decl/src/common/vars.c: -------------------------------------------------------------------------------- 1 | #include "decl.h" 2 | 3 | char token[MAXTOKEN] = {0}; 4 | char name[MAXTOKEN] = {0}; 5 | char datatype[MAXTOKEN] = {0}; 6 | char out[MAXTOKEN] = {0}; 7 | int tokentype = 0; 8 | char* types[] = {"char", "short", "int", "long", "float", "double", "void"}; 9 | -------------------------------------------------------------------------------- /ch-5/decl/src/decl-main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "decl.h" 5 | 6 | int main() { 7 | if (gettype() == -1) { 8 | return -1; 9 | } 10 | out[0] = '\0'; 11 | if (decl() == 0) { 12 | printf("declare %s as%s %s\n", name, out, datatype); 13 | return 0; 14 | } else { 15 | return -1; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch-5/decl/src/undecl-main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "decl.h" 5 | 6 | // undecl: convert word description into declaration 7 | int main() { 8 | int type; 9 | char temp[MAXTOKEN]; 10 | 11 | while (gettoken() != EOF) { 12 | strcpy(out, token); 13 | while ((type = gettoken()) != '\n') { 14 | if (type == PARENS || type == BRACKETS) { 15 | strcat(out, token); 16 | } else if (type == '*') { 17 | if ((type = gettoken()) == PARENS || type == BRACKETS) { 18 | sprintf(temp, "(*%s)", out); 19 | strcat(temp, token); 20 | } else if (type == NAME) { 21 | sprintf(temp, "%s* %s", token, out); 22 | } else if (type == '*') { 23 | sprintf(temp, "**%s", out); 24 | } else { 25 | printf("undecl: syntax error.\n"); 26 | } 27 | strcpy(out, temp); 28 | } else if (type == NAME) { 29 | sprintf(temp, "%s %s", token, out); 30 | strcpy(out, temp); 31 | } else { 32 | printf("invalid input at %s\n", token); 33 | } 34 | } 35 | printf("%s\n", out); 36 | } 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /ch-5/decl/undecl-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # char* x() 4 | echo "In: x () * char" 5 | echo "Expected: char* x()" 6 | printf "Actual: " 7 | echo "x () * char" | ./bin/undecl | grep "char\* x()" 8 | printf "\n" 9 | 10 | # char x[] 11 | echo "In: x [] char" 12 | echo "Expected: char x[]" 13 | printf "Actual: " 14 | echo "x [] char" | ./bin/undecl | grep "char x\[\]" 15 | printf "\n" 16 | 17 | # char (*x)() 18 | # declare x as pointer to function returning char 19 | echo "In: x * () char" 20 | echo "Expected: char (*x)()" 21 | printf "Actual: " 22 | echo "x * () char" | ./bin/undecl | grep "char (\*x)()" 23 | printf "\n" 24 | 25 | # char (*(*x())[])() 26 | # declare x as function returning pointer to array of pointer to function returning char 27 | echo "In: x () * [] * () char" 28 | echo "Expected: char (*(*x())[])()" 29 | printf "Actual: " 30 | echo "x () * [] * () char" | ./bin/undecl | grep "char (\*(\*x())\[\])()" 31 | printf "\n" 32 | 33 | # char** (*x)() 34 | # declare x as pointer to function returning pointer to pointer to char 35 | echo "In: x * () * * char" 36 | echo "Expected: char **(*x)()" 37 | printf "Actual: " 38 | echo "x * () * * char" | ./bin/undecl | grep "char \*\*(\*x)()" 39 | printf "\n" 40 | -------------------------------------------------------------------------------- /ch-5/sort/README.md: -------------------------------------------------------------------------------- 1 | sort 2 | --- 3 | 4 | ### Problem statements 5 | Exercise 5-14. Modify the sort program to handle a -r flag, which indicates 6 | sorting in reverse (decreasing) order. Be sure that -r works with -n. 7 | 8 | Exercise 5-15. Add the option -f to fold upper and lower case together, so 9 | that case distinctions are not made during sorting; for example, a and A 10 | compare equal. 11 | 12 | Exercise 5-16. Add the -d ("directory order") option, which makes 13 | comparisons only on letters, numbers and blanks. Make sure it works in 14 | conjunction with -f. 15 | 16 | Exercise 5-17. Add a field-handling capability, so sorting may be done on 17 | fields within lines, each field sorted according to an independent set of 18 | options. (The index for this book was sorted with -df for the index category 19 | and -n for the page numbers.) 20 | 21 | ### Notes 22 | 5-17: The problem statement for this isn't super clear (what is meant by "fields?" should we go character by character in each string or whitespace separated substrings? can we have multiple subfields sorted differently? etc.), so to keep the problem scope 23 | sane, we will assume that if an `-i` field is given with an offset, any fields after it are to be sub-sorted; `field` means a whitespace-separated substring of characters within that string, e.g. in `pointers, 18 55 15 32`, if `sort ... -i 9 -n` is given, then the substring `18 55 15 32` would be sorted numerically (into `15 18 32 55`) as these subfields occur after index 9. Any flags given after `-i` apply to the sub-sorting routine. 24 | -------------------------------------------------------------------------------- /ch-5/sort/include/sort-tests.h: -------------------------------------------------------------------------------- 1 | void test_parse_args(void); 2 | void test_foldcmp(void); 3 | void test_dir_strip(void); 4 | -------------------------------------------------------------------------------- /ch-5/sort/include/sort.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct input_flags { 4 | bool directory; 5 | bool fold; 6 | bool numeric; 7 | bool reverse; 8 | bool in_use; 9 | char _align[3]; 10 | } input_flags; 11 | 12 | // input 13 | int parse_args(const int argc, char** const argv, input_flags* const p_flags, 14 | input_flags* const s_flags); 15 | 16 | // sort 17 | void myqsort(char** strings, int left, int right, input_flags* flags, 18 | int (*comp)(void*, void*)); 19 | void swap_strs(char** v, const int i, const int j); 20 | 21 | // compare 22 | int dircmp(const char* const s1, const char* const s2); 23 | int foldcmp(const char* const s1, const char* const s2); 24 | int numcmp(const char* const s1, const char* const s2); 25 | void dir_strip(char* const dest, const char* const src, const int n); 26 | 27 | // strings 28 | int split(char* string, int offset, char splitchar, char** substrings); 29 | int join(char** substrings, int count, char* joinstr, char* string); 30 | int indexof(char* string, char character); 31 | -------------------------------------------------------------------------------- /ch-5/sort/sort-test-homedir.txt: -------------------------------------------------------------------------------- 1 | . 2 | .. 3 | .ansible 4 | .bash_history 5 | .bash_logout 6 | .bashrc 7 | .cache 8 | Code 9 | .config 10 | Desktop 11 | DnD 12 | Documents 13 | Downloads 14 | .dropbox 15 | Dropbox 16 | .dropbox-dist 17 | examples.desktop 18 | foo.txt 19 | .gconf 20 | .gitconfig 21 | .gnupg 22 | go 23 | .ICEauthority 24 | .lesshst 25 | .local 26 | .mozilla 27 | .mume 28 | Music 29 | .nano 30 | nothing.c 31 | .nv 32 | Pictures 33 | .pki 34 | .profile 35 | Public 36 | .pulse-cookie 37 | .python_history 38 | scratchpad 39 | simple.py 40 | snap 41 | .ssh 42 | .steam 43 | .steampath 44 | .steampid 45 | .sudo_as_admin_successful 46 | Templates 47 | Videos 48 | .vim 49 | .viminfo 50 | .viminfo.tmp 51 | .viminfx.tmp 52 | .viminfy.tmp 53 | .viminfz.tmp 54 | .vimrc 55 | .vscode 56 | .wget-hsts 57 | -------------------------------------------------------------------------------- /ch-5/sort/sort-test-mixed-case.txt: -------------------------------------------------------------------------------- 1 | Sierra 2 | jUliet 3 | inDia 4 | queEn 5 | YankeE 6 | foxtroT 7 | oScAr 8 | viCTor 9 | pAPa 10 | DElta 11 | UNiform 12 | HOTEL 13 | miKe 14 | noVEMber 15 | ChaRLie 16 | tangO 17 | braVO 18 | whISkey 19 | ZULu 20 | aLPha 21 | eCHO 22 | xRAY 23 | rOMEo 24 | goLF 25 | KiLo 26 | LiMa 27 | -------------------------------------------------------------------------------- /ch-5/sort/sort-test-subfields.txt: -------------------------------------------------------------------------------- 1 | sierra 11 5 0 8 2 | juliet 795 297 116 383 498 3 | india 67 686 4 | queen 710 5 | yankee 223 434 52 6 | foxtrot 131 8 385 259 427 1000 7 | oscar 23 5555 1124 1 8 | victor 344 930 493 423 898 226 9 | papa 320 306 161 467 10 | delta 315 349 909 564 11 | uniform 927 716 708 12 | hotel 493 803 13 | mike 716 685 14 | november 101 23 847 806 387 629 15 | charlie 1 22 55 96 100 16 | tango 821 538 904 696 565 17 | bravo 87 831 733 18 | whiskey 748 698 265 907 339 19 | zulu 377 921 467 511 20 | alpha 871 810 510 345 518 177 21 | echo 609 410 850 519 746 192 22 | xray 113 218 237 43 23 | romeo 17 45 513 777 24 | golf 81 147 19 25 | kilo 188 26 | lima 705 869 823 110 27 | -------------------------------------------------------------------------------- /ch-5/sort/sort-test.txt: -------------------------------------------------------------------------------- 1 | sierra 2 | juliet 3 | india 4 | queen 5 | yankee 6 | foxtrot 7 | oscar 8 | victor 9 | papa 10 | delta 11 | uniform 12 | hotel 13 | mike 14 | november 15 | charlie 16 | tango 17 | bravo 18 | whiskey 19 | zulu 20 | alpha 21 | echo 22 | xray 23 | romeo 24 | golf 25 | kilo 26 | lima 27 | -------------------------------------------------------------------------------- /ch-5/sort/sorted.txt: -------------------------------------------------------------------------------- 1 | alpha 2 | bravo 3 | charlie 4 | delta 5 | echo 6 | foxtrot 7 | golf 8 | hotel 9 | india 10 | juliet 11 | kilo 12 | lima 13 | mike 14 | november 15 | oscar 16 | papa 17 | queen 18 | romeo 19 | sierra 20 | tango 21 | uniform 22 | victor 23 | whiskey 24 | xray 25 | yankee 26 | zulu 27 | -------------------------------------------------------------------------------- /ch-5/sort/src/compare.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "sort.h" 6 | 7 | // dir_strip(): copy up to n bytes from src to dest with all non alphanumeric 8 | // and whitespace removed, and a null terminator. Assumes dest is at least n+1 9 | // bytes long. 10 | void dir_strip(char *const dest, const char *const src, const int n) { 11 | int i, j; 12 | for (i = 0, j = 0; i < n; i++) { 13 | if (isalnum((char)src[i]) || src[i] == ' ') { 14 | dest[j++] = src[i]; 15 | } 16 | } 17 | 18 | if (n > 0) { 19 | dest[j] = '\0'; 20 | } 21 | } 22 | 23 | // foldcmp(): compare s1 and s2, ignoring case 24 | int foldcmp(const char *const s1, const char *const s2) { 25 | int s1_len = (int)strlen(s1); 26 | char *s1_low = (char *)alloca(s1_len + 1); 27 | 28 | int s2_len = (int)strlen(s2); 29 | char *s2_low = (char *)alloca(s2_len + 1); 30 | 31 | int i; 32 | for (i = 0; i < s1_len; i++) { 33 | s1_low[i] = (char)tolower(s1[i]); 34 | } 35 | s1_low[i] = '\0'; 36 | 37 | for (i = 0; i < s2_len; i++) { 38 | s2_low[i] = (char)tolower(s2[i]); 39 | } 40 | s2_low[i] = '\0'; 41 | 42 | return strcmp(s1_low, s2_low); 43 | } 44 | 45 | // numcmp(): compare s1 and s2 numerically 46 | int numcmp(const char *const s1, const char *const s2) { 47 | double v1, v2; 48 | 49 | v1 = atof(s1); 50 | v2 = atof(s2); 51 | 52 | if (v1 < v2) { 53 | return -1; 54 | } else if (v1 > v2) { 55 | return 1; 56 | } else { 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ch-5/sort/src/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sort.h" 7 | 8 | static char usage[] = "usage: sort -d -f -n -r -i "; 9 | 10 | // parse_args(): validate argc/argv; allowed args are -r, -n, -d, and -f 11 | int parse_args(const int argc, char** const argv, input_flags* const p_flags, 12 | input_flags* const s_flags) { 13 | for (int i = 1; i < argc; i++) { 14 | const char* const current_arg = argv[i]; 15 | 16 | if (current_arg[0] != '-') { 17 | printf("sort: illegal arg '%s'\n", argv[i]); 18 | printf("%s\n", usage); 19 | return -1; 20 | } else { 21 | switch (current_arg[1]) { 22 | case 'd': 23 | if (!(s_flags->in_use)) { 24 | p_flags->directory = true; 25 | } else { 26 | s_flags->directory = true; 27 | } 28 | break; 29 | case 'f': 30 | if (!(s_flags->in_use)) { 31 | p_flags->fold = true; 32 | } else { 33 | s_flags->fold = true; 34 | } 35 | break; 36 | case 'n': 37 | if (!(s_flags->in_use)) { 38 | p_flags->numeric = true; 39 | } else { 40 | s_flags->numeric = true; 41 | } 42 | break; 43 | case 'r': 44 | if (!(s_flags->in_use)) { 45 | p_flags->reverse = true; 46 | } else { 47 | s_flags->reverse = true; 48 | } 49 | break; 50 | case 'i': 51 | s_flags->in_use = true; 52 | break; 53 | default: 54 | printf("sort: unknown option '%c'\n", current_arg[1]); 55 | printf("%s\n", usage); 56 | return -1; 57 | } 58 | } 59 | } 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /ch-5/sort/src/sort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "sort.h" 5 | 6 | // myqsort(): quicksort v into increasing order 7 | void myqsort(char **strings, int left, int right, input_flags *flags, 8 | int (*comp)(void *, void *)) { 9 | int i, last; 10 | 11 | // Quit if empty array 12 | if (left >= right) { 13 | return; 14 | } 15 | 16 | swap_strs(strings, left, (left + right) / 2); 17 | 18 | char *string_i, *string_left; 19 | last = left; 20 | for (i = left + 1; i <= right; i++) { 21 | if (flags->directory) { 22 | // If we're doing directory sorting, create temporary stripped 23 | // versions of the strings and do the comparisons based on that. 24 | int i_len = (int)strlen(strings[i]); 25 | string_i = alloca((unsigned int)i_len); 26 | dir_strip(string_i, strings[i], i_len); 27 | 28 | int left_len = (int)strlen(strings[left]); 29 | string_left = alloca((unsigned int)left_len); 30 | dir_strip(string_left, strings[left], left_len); 31 | } else { 32 | // Otherwise, do string comparisons as-is. 33 | string_i = strings[i]; 34 | string_left = strings[left]; 35 | } 36 | if ((*comp)(string_i, string_left) < 0 && !(flags->reverse)) { 37 | swap_strs(strings, ++last, i); 38 | } else if ((*comp)(string_i, string_left) > 0 && flags->reverse) { 39 | swap_strs(strings, ++last, i); 40 | } 41 | } 42 | 43 | swap_strs(strings, left, last); 44 | 45 | // recursively sort subarrays 46 | myqsort(strings, left, last - 1, flags, comp); 47 | myqsort(strings, last + 1, right, flags, comp); 48 | } 49 | 50 | // swap(): interchange v[i] and v[j] in char** v 51 | void swap_strs(char **v, const int i, const int j) { 52 | void *temp; 53 | 54 | temp = v[i]; 55 | v[i] = v[j]; 56 | v[j] = temp; 57 | } 58 | -------------------------------------------------------------------------------- /ch-5/sort/src/strings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "sort.h" 7 | 8 | // index(): get first occurence of character in string 9 | int indexof(char* string, char character) { 10 | for (int i = 0; i < (int)strlen(string); i++) { 11 | // printf("indexof() | evaluating %c\n", string[i]); 12 | if (string[i] == character) { 13 | return i; 14 | } 15 | } 16 | return -1; 17 | } 18 | 19 | // split(): given a string and an offset into it, 20 | // divide the string into substrings divided by splitchar, 21 | // and return the number of them created (analagous to Python's str.split() 22 | // method) 23 | int split(char* string, int offset, char splitchar, char** substrings) { 24 | if (string == NULL) { 25 | return -1; 26 | } 27 | if (substrings == NULL) { 28 | return -1; 29 | } 30 | 31 | int len = (int)strlen(string); 32 | if (offset >= len) { 33 | return -1; 34 | } 35 | 36 | int j, k; 37 | for (j = 0, k = 0; offset < len; offset++) { 38 | // get memory for next substring; string is never longer 39 | // than MAXLEN, so it won't be possible for any substring to exceed 40 | // this length 41 | // TODO: Catch this malloc if it fails 42 | substrings[j] = malloc(MAXLEN * sizeof(char)); 43 | char* current = substrings[j++]; 44 | 45 | // copy characters out of string until we hit a splitchar or null 46 | // terminator 47 | while (string[offset] != splitchar && string[offset] != '\n' && 48 | string[offset] != '\0') { 49 | current[k++] = string[offset++]; 50 | } 51 | current[k] = '\0'; 52 | k = 0; 53 | } 54 | 55 | return j; 56 | } 57 | 58 | // split(): given an array of strings, combine them into one 59 | // string with each substring divided by joinstr (similar 60 | // to python's str.join() method), followed by a null terminator; 61 | // string is assumed to be of sufficient size and will be overflowed if too 62 | // small. We have to 63 | int join(char** substrings, int count, char* joinstr, char* string) { 64 | if (substrings == NULL || joinstr == NULL || string == NULL) { 65 | return -1; 66 | } 67 | 68 | for (int i = 0; i < count; i++) { 69 | strcat(string, substrings[i]); 70 | strcat(string, joinstr); 71 | } 72 | 73 | // remove last joinchar 74 | string[strlen(string) - 1] = '\n'; 75 | string[strlen(string)] = '\0'; 76 | 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /ch-5/src/5.1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Ex 5.1: As written, getint treats a + or - not followed by a digit as a valid 5 | // representation of zero. Fix it to push such a character back on the input. 6 | 7 | // getint: get the next inter from input into *pn 8 | static int getint(int* const pn) { 9 | int c, sign; 10 | 11 | while (isspace(c = getchar())) { 12 | // no op; skip whitespace 13 | } 14 | 15 | // If the first char after whitespace isn't a number, 16 | // we aren't reading a number. 17 | if (!isdigit(c) && c != EOF && c != '+' && c != '-') { 18 | ungetc(c, stdin); 19 | fprintf(stderr, "getint() | error: got invalid char %c in input\n", c); 20 | return -1; 21 | } 22 | 23 | // Determine sign and skip sign char 24 | sign = (c == '-') ? -1 : 1; 25 | 26 | // catch case for "-" being the only input 27 | if (c == '-') { 28 | c = getchar(); 29 | if (c == '\n' || c == EOF) { 30 | fprintf(stderr, "getint() | error: got only '-'\n"); 31 | ungetc('-', stdin); 32 | return -1; 33 | } 34 | } 35 | 36 | // Get each number, keeping a running product 37 | for (*pn = 0; isdigit(c); c = getchar()) { 38 | *pn = 10 * *pn + (c - '0'); 39 | } 40 | 41 | *pn *= sign; 42 | 43 | if (c != EOF && c != '\n') { 44 | ungetc(c, stdin); 45 | fprintf(stderr, "getint() | error: got invalid char %d in post-input\n", c); 46 | return -1; 47 | } 48 | 49 | return 0; 50 | } 51 | 52 | int main() { 53 | int result = 0; 54 | if ((result = getint(&result)) == -1) { 55 | fprintf(stderr, "%c was pushed back to stdin.\n", getchar()); 56 | return 0; 57 | } 58 | printf("%d\n", result); 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /ch-5/src/5.2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Ex. 5.2: Write getfloat(), the floating-point analog of getint(). What type 6 | // does getfloat return as its function value? 7 | 8 | // getint: get the next inter from input into *pn 9 | static int getdouble(double *const pd) { 10 | int c, sign; 11 | 12 | while (isspace(c = getchar())) { 13 | // no op; skip whitespace 14 | } 15 | 16 | // If the first char after whitespace isn't a number, 17 | // we aren't reading a number. 18 | if (!isdigit(c) && c != EOF && c != '+' && c != '-' && c != '.') { 19 | ungetc(c, stdin); 20 | return 0; 21 | } 22 | 23 | // Determine sign and skip sign char; catch case for "-" being the only input 24 | sign = (c == '-') ? -1 : 1; 25 | if (c == '-') { 26 | c = getchar(); 27 | if (c == '\n' || c == EOF) { 28 | printf("getdouble() | Error: '-' is invalid.\n"); 29 | ungetc('-', stdin); 30 | return -1; 31 | } 32 | } 33 | 34 | // Get each number, keeping a running product 35 | for (*pd = 0; isdigit(c); c = getchar()) { 36 | *pd = 10 * *pd + (c - '0'); 37 | } 38 | 39 | // Get fractional part by successively dividing each character 40 | // past the decimal by the appropriate power of 10 and adding 41 | // to a running sum; 42 | if (c == '.') { 43 | double digit, fractional, divisor; 44 | double ten_pow = 1; 45 | 46 | c = getchar(); 47 | for (fractional = 0.0; isdigit(c); c = getchar(), ten_pow++) { 48 | digit = (c - '0'); 49 | divisor = 1 / (pow(10, ten_pow)); 50 | fractional += digit * divisor; 51 | } 52 | *pd += fractional; 53 | } 54 | 55 | *pd *= sign; 56 | 57 | if (c != EOF && c != '\n') { 58 | ungetc(c, stdin); 59 | printf("getdouble() | Got invalid character %c in post-input.\n", c); 60 | return -1; 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | int main() { 67 | double result = 0.0; 68 | if (getdouble(&result) == -1) { 69 | fprintf(stderr, "%c was pushed back to stdin.\n", getchar()); 70 | return 0; 71 | } 72 | printf("%f\n", result); 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /ch-5/src/5.3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Write a program to concatenate a string at 6 | // the end of another string. 7 | //-- 8 | // Since K&R talked about alloc already(), 9 | // I'm going to use malloc() here - 10 | // we cannot just append t to the end of s, because 11 | // we don't know if s has enough space for all 12 | // the characters; we could mandate that a third 13 | // array is passed in that does have sufficient capacity, 14 | // but doing this is probably the safest and most concise 15 | // approach. 16 | 17 | static char* my_strcat(const char* const s, const unsigned int s_len, 18 | const char* const t, const unsigned int t_len) { 19 | int i = 0; 20 | char* new = malloc(sizeof(char) * (s_len + t_len + 1)); 21 | 22 | for (unsigned int j = 0; j < s_len; j++) { 23 | new[i++] = s[j]; 24 | } 25 | 26 | for (unsigned int k = 0; k < t_len; k++) { 27 | new[i++] = t[k]; 28 | } 29 | 30 | new[i] = '\0'; 31 | return new; 32 | } 33 | 34 | int main() { 35 | char first[] = {"Joshua "}; // len = 7 36 | char last[] = {"Goller"}; // len = 6 37 | char* full_name = my_strcat(first, (unsigned int)strlen(first), last, 38 | (unsigned int)strlen(last)); 39 | printf("My name is: %s.\n", full_name); 40 | free(full_name); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /ch-5/src/5.4.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Ex 5.4: Write a program strend(s, t) that returns 1 if string t is 4 | // found at the end of string s, and 0 otherwise. 5 | 6 | static int strend(const char *const s, const unsigned int s_len, 7 | const char *const t, const unsigned int t_len) { 8 | // By convention, a null-terminated string is found at the end of any string. 9 | if (t_len == 0) { 10 | return 1; 11 | } 12 | 13 | // The only string at the end of a null string is another null string. 14 | if (s_len == 0) { 15 | return (t_len ? 0 : 1); 16 | } 17 | 18 | // Starting from the end of each string, compare char-by-char; if we reach 19 | // zero for t_len before or at the same time as s_len, it's at the end of s; 20 | // otherwise, it isn't. 21 | unsigned int t_end = t_len - 1; 22 | unsigned int s_end = s_len - 1; 23 | while (s[s_end] == t[t_end]) { 24 | // if we hit the end of s first, t is not at the end of s unless t == s (and 25 | // thus t_end is also zero) 26 | if (s_end == 0) { 27 | return (t_end ? 0 : 1); 28 | } 29 | 30 | // if we hit t_end first, it is at the end of s. 31 | if (t_end == 0) { 32 | return 1; 33 | } 34 | 35 | t_end--; 36 | s_end--; 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | int main() { 43 | printf("Joshua and Goller: %d\n", strend("Joshua", 6, "Goller", 6)); 44 | printf("Joshua and shua: %d\n", strend("Joshua", 6, "shua", 4)); 45 | printf("Joshua and empty string: %d\n", strend("Joshua", 6, "", 0)); 46 | printf("empty string and Joshua: %d\n", strend("", 0, "Joshua", 6)); 47 | printf("two empty strings: %d\n", strend("", 0, "", 0)); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /ch-5/src/5.6.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | Ex. 5.6: Rewrite appropriate programs from earlier chapters and exercises with 8 | pointers instead of array indexing. Good possibilities include getline (Chapters 9 | 1 and 4), atoi, itoa, and their variants (Chapters 2, 3, and 4), reverse 10 | (Chapter 3), and strindex and getop (Chapter 4). 11 | */ 12 | 13 | static void reverse_p(char* const str, const int len) { 14 | char *first, *last; 15 | char temp; 16 | 17 | for (first = str, last = str + len - 1; first <= last; first++, last--) { 18 | temp = *last; 19 | *last = *first; 20 | *first = temp; 21 | } 22 | } 23 | 24 | static void test(char* const input, const char* const expected, 25 | const char* const message) { 26 | const int len = (int)strlen(input); 27 | char* copy = (char*)malloc((unsigned long)len + 1); 28 | strcpy(copy, input); 29 | 30 | reverse_p(copy, len); 31 | assert_string_eq(copy, expected, "reverse", message); 32 | free(copy); 33 | } 34 | 35 | int main() { 36 | test("Joshua", "auhsoJ", "even input"); 37 | test("Steve", "evetS", "odd input"); 38 | test("a", "a", "single input"); 39 | test("", "", "null input"); 40 | printf("5.6: PASS!\n"); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /ch-5/tabber/README.md: -------------------------------------------------------------------------------- 1 | entab/detab 2 | --- 3 | 4 | ### Exercise directions 5 | Exercise 5-11: Modify the programs entab and detab (written as exercises in Chapter 1) to 6 | accept a list of tab stops as arguments. Use the default tab settings if there are no arguments. 7 | 8 | Exercise 5-12: Extend entab and detab to accept the shorthand `entab -m x+y` to mean tab stops every x columns, starting at column y. Choose convenient (for the user) default behavior. 9 | 10 | ### Prompts from original entab / detab 11 | Ex 1.20: Write a program detab() that replaces tabs in the input with the 12 | proper number of blanks to space to the next tab stop. Assume a fixed set of 13 | tab stops, say every n columns. Should n be a variable or a symbolic parameter? 14 | 15 | Ex 1.21: Write a program entab that replaces strings of blanks by the minimum number 16 | of tabs and blanks to achieve the same spacing. Use the same tab stops as for 17 | detab(). When either a tab or a single blank would suffice to reach a tab 18 | stop, which should be given preference? 19 | 20 | ### Assumptions 21 | * We will not allow for tab stops after 80 characters 22 | * I have slightly modified the shorthand for `-m` to be more intuitive to the user. 23 | -------------------------------------------------------------------------------- /ch-5/tabber/include/tabber.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define USE_ENTAB 42 4 | #define USE_DETAB 666 5 | 6 | // input 7 | int parse_flags(const int argc, char** const argv, int** tab_stops, 8 | int* stop_list_len); 9 | 10 | // detab 11 | char* detab(const char* const in_line, const int in_len, 12 | const int* const stop_list, const int stop_list_len); 13 | // entab 14 | char* entab(const char* const in_line, const int in_len, 15 | const int* const tab_stop, const int stop_list_len); 16 | int look_ahead(const char* const in_line, const int in_len, int offset, 17 | const int stop_list); 18 | // misc 19 | int generate_stop_list(const int start, const int interval, int** tab_stops, 20 | int* const tab_stops_len); 21 | int next_tab_stop(const int* const tab_stops, const int tab_stops_len, 22 | const int column); 23 | int isdigits(const char* const string, const int len); 24 | -------------------------------------------------------------------------------- /ch-5/tabber/src/detab.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "tabber.h" 7 | 8 | #define WHITESPACE '.' 9 | 10 | // detab(): replace tabs in in_line with with 11 | // whitespace up to next tab_stop-th column 12 | char* detab(const char* const in_line, const int in_len, 13 | const int* const tab_stops, const int tab_stops_len) { 14 | char* out_line; 15 | int i, j; 16 | char temp[MAXLEN]; 17 | int tab_stop = 0; 18 | 19 | // go through in_line until a tab is found and convert 20 | // to appropriate number of whitespace up to tab stop 21 | for (j = i = 0; i < in_len; i++) { 22 | tab_stop = next_tab_stop(tab_stops, tab_stops_len, j); 23 | 24 | if (in_line[i] != '\t') { 25 | temp[j] = in_line[i]; 26 | j++; 27 | } else { 28 | while (j < tab_stop) { 29 | temp[j] = WHITESPACE; 30 | j++; 31 | } 32 | } 33 | } 34 | temp[j] = '\0'; 35 | 36 | out_line = (char*)malloc((unsigned long)j + 1); 37 | strncpy(out_line, temp, (unsigned long)j + 1); 38 | return out_line; 39 | } 40 | -------------------------------------------------------------------------------- /ch-5/tabber/src/entab.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "tabber.h" 7 | 8 | #define TAB_CHAR '$' 9 | 10 | // entab(): given a string of len, convert all strings of whitespaces into tabs 11 | // so that it conforms with tab stops. Example (whitespace represented with 12 | // '.'): 13 | // 14 | 15 | // return it with all whitespaces of a given 16 | // length replaced by tabs (represented by "$" for clarity) 17 | char* entab(const char* const in_line, const int in_len, 18 | const int* const tab_stops, const int tab_stops_len) { 19 | char* out_line = {""}; 20 | int i = 0, j = 0, next_stop = 0; 21 | char temp[MAXLEN] = {0}; 22 | 23 | // Copy char by char til we get a whitespace; if so, 24 | // look_ahead() to see if we can entab - do so if possible, 25 | // otherwise just copy the char. 26 | for (j = i = 0; i < in_len; i++) { 27 | next_stop = next_tab_stop(tab_stops, tab_stops_len, i); 28 | if (in_line[i] == ' ') { 29 | if ((next_stop = look_ahead(in_line, in_len, i, next_stop))) { 30 | temp[j] = TAB_CHAR; 31 | j++; 32 | i = next_stop - 1; 33 | } else { 34 | temp[j] = in_line[i]; 35 | j++; 36 | }; 37 | } else { 38 | temp[j] = in_line[i]; 39 | j++; 40 | } 41 | } 42 | temp[j] = '\0'; 43 | 44 | out_line = strdup(temp); 45 | return out_line; 46 | } 47 | 48 | /* 49 | look_ahead(): given string in_line of length in_len, determine if 50 | the characters between in_line[offset] and the nearest tab_stop are 51 | all whitespaces. If they are, return the next tab stop's offset; otherwise, 52 | return 0. 53 | */ 54 | int look_ahead(const char* const in_line, const int in_len, int offset, 55 | const int tab_stop) { 56 | int next_stop; 57 | 58 | next_stop = offset + (tab_stop - (offset % tab_stop)); 59 | 60 | // Quit if we try to read past the buffer 61 | if (next_stop > in_len) { 62 | return 0; 63 | } 64 | for (; offset < next_stop; offset++) { 65 | if (in_line[offset] != ' ') { 66 | return 0; 67 | } 68 | } 69 | return next_stop; 70 | } 71 | -------------------------------------------------------------------------------- /ch-5/tabber/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "common.h" 5 | #include "tabber.h" 6 | 7 | int main(int argc, char** argv) { 8 | int len = 0; // current line length 9 | char line[MAXLEN] = {0}; // current input line 10 | char* processed_line = NULL; // line after entabing / detabing 11 | int* tab_stops = NULL; // list of tab stops 12 | int tab_stops_len = 0; 13 | char* (*behavior)(const char* const, const int, const int* const, 14 | const int); // detab or entab, depending on flags 15 | 16 | // handle input 17 | switch (parse_flags(argc, argv, &tab_stops, &tab_stops_len)) { 18 | case USE_ENTAB: 19 | behavior = entab; 20 | break; 21 | case USE_DETAB: 22 | behavior = detab; 23 | break; 24 | default: 25 | if (tab_stops != NULL) { 26 | free(tab_stops); 27 | } 28 | return -1; 29 | } 30 | 31 | // do the thing 32 | while ((len = mygetline(line, MAXLEN)) > 0) { 33 | processed_line = behavior(line, len, tab_stops, tab_stops_len); 34 | printf("%s\n", processed_line); 35 | free(processed_line); 36 | } 37 | 38 | free(tab_stops); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /ch-5/tabber/src/misc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tabber.h" 6 | 7 | int generate_stop_list(const int start, const int interval, int** tab_stops, 8 | int* const tab_stops_len) { 9 | int* stop_list; 10 | int stop_list_len; 11 | 12 | if (*tab_stops != NULL || *tab_stops_len != 0) { 13 | printf( 14 | "generate_stop_list() | error - tab stops have already been generated; " 15 | "quitting.\n"); 16 | return -1; 17 | } 18 | 19 | if ((stop_list = malloc(sizeof(int) * 80)) == NULL) { 20 | printf("generate_stop_list() | couldn't allocate stop list.\n"); 21 | return -1; 22 | } 23 | 24 | stop_list_len = 0; 25 | for (int i = start; i < 80; i++) { 26 | if ((i % interval) == 0) { 27 | stop_list[stop_list_len++] = i; 28 | } 29 | } 30 | 31 | *tab_stops = stop_list; 32 | *tab_stops_len = stop_list_len; 33 | return 0; 34 | } 35 | 36 | // next_tab_stop(): given a list of tab stops and a current 37 | // column, determine what the next tab stop is 38 | int next_tab_stop(const int* const tab_stops, const int tab_stops_len, 39 | const int column) { 40 | if (tab_stops == NULL || tab_stops_len < 1) { 41 | return -1; 42 | } 43 | if (tab_stops_len == 1) { 44 | return tab_stops[0]; 45 | } 46 | 47 | int prev_stop = 0; 48 | int next_stop = 0; 49 | for (int i = 0; i < tab_stops_len; i++) { 50 | next_stop = tab_stops[i]; 51 | if ((prev_stop <= column) && (column < next_stop)) { 52 | // printf("next_tab_stop() | column: %d, next_stop: %d\n", column, 53 | // next_stop); 54 | return next_stop; 55 | } 56 | prev_stop = next_stop; 57 | } 58 | 59 | return -1; 60 | } 61 | 62 | // isdigits(): if string contains only digits, return 1; 63 | // otherwise return 0. 64 | int isdigits(const char* const string, const int len) { 65 | for (int i = 0; i < len; i++) { 66 | if (isdigit(string[i]) == 0) { 67 | return 0; 68 | } 69 | } 70 | return 1; 71 | } 72 | -------------------------------------------------------------------------------- /ch-5/tabber/tabber-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function valg(){ 4 | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 $1 5 | } 6 | 7 | ./bin/tabber 8 | printf "a b" | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b entab 9 | printf "a\t\tb" | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b detab 10 | printf "a b" | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b detab -l 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 11 | printf "a\t\tb" | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b entab -l 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 12 | printf "a b" | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b entab -m 5+10 13 | printf "a\t\tb" | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b detab -m 5+10 14 | 15 | # -m and -l are exclusive, so this should return 255 16 | valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=42 ./bin/tabber -b detab -m 10+5 -l 10 15 20 17 | if [ $? == 255 ]; then 18 | exit 0; 19 | else 20 | exit 1; 21 | fi 22 | -------------------------------------------------------------------------------- /ch-5/tail/include/tail.h: -------------------------------------------------------------------------------- 1 | typedef struct node { 2 | struct node* next; 3 | char* data; 4 | } node; 5 | 6 | typedef struct { 7 | node* head; 8 | node* tail; 9 | int current_size; 10 | int max_size; 11 | } queue; 12 | 13 | int check_input(const int argc, const char* const* const argv); 14 | int read_lines(queue* const q); 15 | 16 | // queue.c 17 | queue* new_queue(const int max_size); 18 | int enqueue(queue* const q, char* const data); 19 | int dequeue(queue* const q, char* data); 20 | -------------------------------------------------------------------------------- /ch-5/tail/src/queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "tail.h" 7 | 8 | #pragma clang diagnostic ignored "-Wcast-qual" 9 | 10 | // new_queue(): Initialize a queue structure 11 | queue* new_queue(const int max_size) { 12 | queue* q = malloc(sizeof(queue)); 13 | 14 | q->head = NULL; 15 | q->tail = NULL; 16 | q->max_size = max_size; 17 | q->current_size = 0; 18 | 19 | return q; 20 | } 21 | 22 | // enqueue(): add an item to the queue 23 | int enqueue(queue* const q, char* const data) { 24 | if (q->current_size == q->max_size) { 25 | // If the queue is full, dequeue the last item to make room 26 | char* deleted = malloc(MAXLEN); 27 | dequeue(q, deleted); 28 | // handle special edgecase for one-node-max queue 29 | if (q->max_size == 1) { 30 | q->head = NULL; 31 | } 32 | free(deleted); 33 | } else if (q->current_size > q->max_size) { 34 | printf("enqueue(): Error - queue exceeds maximum size.\n"); 35 | free(data); 36 | return -1; 37 | } 38 | 39 | // Make a new node 40 | node* new_tail = malloc(sizeof(node)); 41 | new_tail->next = NULL; 42 | new_tail->data = (char*)data; 43 | 44 | // Add the node; handle empty queue if necessary. 45 | if (q->head == NULL) { 46 | q->head = new_tail; 47 | q->tail = new_tail; 48 | } 49 | q->tail->next = new_tail; 50 | q->tail = q->tail->next; 51 | q->current_size++; 52 | 53 | return 0; 54 | } 55 | 56 | // dequeue(): remove an item from the queue 57 | int dequeue(queue* const q, char* const data) { 58 | if (!(q->current_size && q->head)) { 59 | // Queue is empty 60 | *data = '\0'; 61 | return 0; 62 | } else { 63 | // Remove current head; make the next item the new head 64 | node* old_head = q->head; 65 | strcpy(data, old_head->data); 66 | q->head = old_head->next; 67 | 68 | free(old_head->data); 69 | free(old_head); 70 | q->current_size--; 71 | return 1; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ch-5/tail/tail-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is explicitly for testing the 5.13 exercise for replicating 3 | # the GNU tail program and should be called via the Makefile. 4 | 5 | TESTFILE=tail-test.txt 6 | 7 | function clean_up(){ 8 | rm $TESTFILE 9 | } 10 | trap clean_up EXIT 11 | 12 | function fatal(){ 13 | local MESSAGE=${1} 14 | echo $MESSAGE 15 | echo "5.13: FAIL." 16 | exit 17 | } 18 | 19 | function tail_test(){ 20 | if [[ -z ${1} ]] 21 | then 22 | local N="" 23 | else 24 | local N="-n ${1}" 25 | fi 26 | 27 | local EXPECTED_OUT_LINES=${2} 28 | local TEST_NAME=${3} 29 | 30 | 31 | ACTUAL_OUT_LINES=$(cat $TESTFILE | valgrind -q --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=42 bin/tail $N 2>&1 | wc -l ) 32 | 33 | if [[ $ACTUAL_OUT_LINES -ne $EXPECTED_OUT_LINES || $? -eq 42 ]] 34 | then 35 | fatal "Test failure, $TEST_NAME: $ACTUAL_OUT_LINES != $EXPECTED_OUT_LINES (check Valgrind output)" 36 | fi 37 | } 38 | 39 | function generate_test_file(){ 40 | local SIZE=${1} 41 | for LINE in `seq 1 $SIZE`; do 42 | echo $LINE >> $TESTFILE; 43 | done 44 | } 45 | 46 | generate_test_file 20 47 | tail_test "" 10 "basic input" 48 | tail_test 25 20 "larger n" 49 | tail_test 1 1 "n = 1" 50 | tail_test 0 0 "n = 0" 51 | 52 | #clean_up 53 | #echo "Generating huge input for tail; this is slow." 54 | #generate_test_file 20000 55 | #echo "Huge input generated." 56 | #tail_test "" 10 "Huge input" 57 | 58 | echo "5.13: PASS!" 59 | -------------------------------------------------------------------------------- /ch-6/Makefile: -------------------------------------------------------------------------------- 1 | # kwcount is 6.1 2 | kwcount: clean 3 | $(COMPILE) -I ch-6/$@/include/ ch-6/$@/src/*.c -o bin/$@ 4 | cat ch-6/$@/$@-test.c | $(VALGRIND) ./bin/$@ 5 | cat ch-6/$@/src/$@.c | $(VALGRIND) ./bin/$@ 6 | 7 | # charmatch is 6.2 8 | charmatch: clean 9 | $(COMPILE) -I ch-6/$@/include/ ch-6/$@/src/*.c -o bin/$@ 10 | cat ch-6/$@/$@-test.* | $(VALGRIND) ./bin/$@ 1 11 | 12 | # crossref is 6.3 13 | crossref: clean 14 | $(ANALYZER) $(CC) $(CFLAGS) $(WARNINGS) $(EXTRA_FLAGS) -I ch-6/$@/include/ ch-6/$@/src/*.c -o bin/$@ 15 | cat ch-6/$@/$@-test-full.txt | $(VALGRIND) ./bin/$@ 16 | cat ch-6/$@/$@-test-part.txt | $(VALGRIND) ./bin/$@ 17 | 18 | # wordcount is 6.4 19 | # hashtable is 6.5 and 6.6 20 | wordcount hashtable: clean 21 | $(COMPILE) -I ch-6/$@/include/ ch-6/$@/src/*.c -o bin/$@ 22 | cat ch-6/$@/$@-test.* | $(VALGRIND) ./bin/$@ 23 | -------------------------------------------------------------------------------- /ch-6/README.md: -------------------------------------------------------------------------------- 1 | Ch-6 2 | --- 3 | 4 | ## Notes about the exercises 5 | * `charmatch` 6 | * I wound up narrowing the scope of this problem significantly and ignoring a few legitimate edge cases; I would need to write a complete C parser and preprocessor to catch every possible variable, which is out of scope. 7 | * I left stubbed out code in for handling `typedefs` and `#defines`; I consider this in-scope and will return to it. 8 | * `crossref` 9 | * I spent a few days chasing memory-related bugs in this program, so I didn't bother trying to filter out `noise words` from the final output. 10 | * I copied and modified the tree code in `crossref` into `common/` to be usable as a generic tree library for 6.4 and other exercises, but I didn't bother making 6.3 use it; I can and probably should refactor 6.3 if I ever get around to it. 11 | -------------------------------------------------------------------------------- /ch-6/charmatch/charmatch-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define def_x int 4 | #define z_string "int z0 is not a variable; this is a string" 5 | 6 | // int z1; is not a variable, this is a CPP comment 7 | 8 | /* int z2; is not a variable, this is a C comment */ 9 | 10 | typedef td_y int; 11 | 12 | int foobar(float float_a, float float_b) { printf("%f\n", a + b); } 13 | 14 | int main() { 15 | int alpha1; 16 | int beta1; 17 | int gamma1, alpha12; 18 | int beta2 = 0; 19 | int gamma2 = 0; 20 | int alpha3 = 0, beta3 = 0, gamma3 = 0; 21 | char alpha4[] = "int z3 is not a variable"; 22 | printf("int z4 is not a variable"); 23 | td_y a5; 24 | 25 | // prints garbage as multiple vars aren't initialized 26 | printf("%d\n", alpha1 + alpha2 + alpha3 + beta1 + beta2 + beta3 + gamma1 + 27 | gamma2 + gamma3); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /ch-6/charmatch/include/charmatch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct string { 6 | struct string* next_group; 7 | struct string* next_string; 8 | char* chars; 9 | } string; 10 | 11 | typedef string type_name; 12 | typedef string var_name; 13 | 14 | extern int MATCH_LENGTH; 15 | 16 | // fsm 17 | extern bool IN_CPP_COMMENT; 18 | extern bool IN_C_COMMENT; 19 | extern bool IN_STRING; 20 | extern bool IN_FUNCTION_PARAMS; 21 | 22 | #define SHOULD_STORE \ 23 | !(IN_CPP_COMMENT | IN_C_COMMENT | IN_STRING | IN_FUNCTION_PARAMS) 24 | 25 | void update_fsm(const char* const token); 26 | 27 | // vars 28 | void parse_varname(void); 29 | void display_varnames(void); 30 | void cleanup_varnames(void); 31 | int store_varname(const char* const varname); 32 | 33 | // types 34 | int istypename(const char* const word); 35 | int handle_define(void); 36 | void handle_typedef(void); 37 | void cleanup_typenames(void); 38 | 39 | // misc 40 | void skip_whitespace(void); 41 | int parse_input(const int argc, char** const argv); 42 | int gettoken(char* const word, const int len); 43 | 44 | // strings 45 | string* alloc_string(const char* const characters); 46 | void free_string(string* str); 47 | string* walk_strings_ll(const string* const head, const char* const str); 48 | string* walk_groups_ll(const string* const head, const char* const str, 49 | const int n); 50 | -------------------------------------------------------------------------------- /ch-6/charmatch/src/fsm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "charmatch.h" 6 | 7 | bool IN_CPP_COMMENT = false; // this kind of comment 8 | bool IN_C_COMMENT = false; /* this kind of comment */ 9 | bool IN_STRING = false; 10 | bool IN_FUNCTION_PARAMS = false; 11 | 12 | void update_fsm(const char* const token) { 13 | int c = 0; 14 | int token_len = (int)strlen(token); 15 | if (token == NULL || token_len < 1) { 16 | return; 17 | } 18 | 19 | // Crappy way to test for function parameters; there's 20 | // quite a few places where "something(" might not represent 21 | // a function declaration 22 | if (token[token_len - 1] == '(') { 23 | IN_FUNCTION_PARAMS = true; 24 | } 25 | 26 | if ((strcmp(token, ")") == 0) && IN_FUNCTION_PARAMS) { 27 | IN_FUNCTION_PARAMS = false; 28 | return; 29 | } 30 | 31 | if (strcmp(token, "\"") == 0) { 32 | IN_STRING = !IN_STRING; 33 | return; 34 | } 35 | 36 | if (strcmp(token, "/") == 0) { 37 | if ((c = getchar()) == '/') { 38 | IN_CPP_COMMENT = true; 39 | } else if (c == '*') { 40 | IN_C_COMMENT = true; 41 | } else { 42 | ungetc(c, stdin); 43 | } 44 | } 45 | 46 | if (strcmp(token, "*") == 0 && IN_C_COMMENT) { 47 | if ((c = getchar()) == '/') { 48 | IN_C_COMMENT = false; 49 | } else { 50 | ungetc(c, stdin); 51 | } 52 | } 53 | 54 | if (strcmp(token, "\n") == 0) { 55 | IN_CPP_COMMENT = false; 56 | return; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ch-6/charmatch/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "charmatch.h" 5 | #include "common.h" 6 | 7 | int MATCH_LENGTH; 8 | 9 | /* 10 | Maybe it would be best to move gettoken() into this program, and then have 11 | it where each call to gettoken() updates the FSM, and only store_var() actually 12 | checks to see if the current FSM state allows for vars? Otherwise, assume all 13 | parts of the C program are valid places for vars to occur? 14 | */ 15 | 16 | int main(int argc, char** argv) { 17 | if ((MATCH_LENGTH = parse_input(argc, argv)) < 1) { 18 | return -1; 19 | } 20 | 21 | int i = 0; 22 | char word[MAXLEN] = {0}; 23 | while ((i = gettoken(word, MAXLEN)) > 0) { 24 | if (istypename(word)) { 25 | parse_varname(); 26 | } else if (strcmp(word, "#define") == 0) { 27 | handle_define(); 28 | } else if (strcmp(word, "typedef") == 0) { 29 | handle_typedef(); 30 | } 31 | } 32 | 33 | display_varnames(); 34 | cleanup_varnames(); 35 | cleanup_typenames(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /ch-6/charmatch/src/misc.c: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wcast-qual" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "charmatch.h" 8 | 9 | static char usage[] = {"usage: charmatch "}; 10 | 11 | int parse_input(const int argc, char** const argv) { 12 | int length = 0; 13 | 14 | if (argc != 2) { 15 | printf("Error: must provide a match length argument.\n"); 16 | printf("%s\n", usage); 17 | return 0; 18 | } 19 | 20 | if ((length = atoi(argv[1])) < 1) { 21 | printf("Error: match length argument must be a number.\n"); 22 | printf("%s\n", usage); 23 | return 0; 24 | } 25 | 26 | return length; 27 | } 28 | 29 | void skip_whitespace() { 30 | int c; 31 | while (isspace(c = getchar())) { 32 | } 33 | ungetc(c, stdin); 34 | } 35 | 36 | // gettoken(): similar to getword(), except that gettoken() looks for characters 37 | // that are syntactically relevant to a C program instead of just alphanumeric 38 | // words and ignores whitespace. Always reads at least one valid character or 39 | // EOF character, but will stop parsing when it reads the first non-alphanum 40 | // character. Returns number of chars read. 41 | int gettoken(char* const word, const int len) { 42 | int c = 0; 43 | int i = 0; 44 | 45 | while (((c = getchar()) != EOF) && i < len) { 46 | if (!isalnum(c) && c != '_') { 47 | if (i == 0) { 48 | word[i++] = (char)c; // always read at least one char 49 | break; 50 | } else { 51 | ungetc(c, stdin); 52 | break; 53 | } 54 | } else { 55 | word[i++] = (char)c; 56 | } 57 | } 58 | word[i] = '\0'; 59 | update_fsm(word); 60 | return i; 61 | } 62 | -------------------------------------------------------------------------------- /ch-6/charmatch/src/strings.c: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wcast-qual" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "charmatch.h" 8 | 9 | string* alloc_string(const char* const characters) { 10 | string* str; 11 | if ((str = malloc(sizeof(string))) == NULL) { 12 | return NULL; 13 | } 14 | 15 | if ((str->chars = strdup(characters)) == NULL) { 16 | free(str); 17 | return NULL; 18 | } 19 | 20 | str->next_group = NULL; 21 | str->next_string = NULL; 22 | return str; 23 | } 24 | 25 | void free_string(string* str) { 26 | free(str->chars); 27 | free(str); 28 | } 29 | 30 | string* walk_groups_ll(const string* const head, const char* const str, 31 | const int n) { 32 | const string* current = head; 33 | while (current != NULL) { 34 | if (strncmp(current->chars, str, (unsigned long)n) == 0) { 35 | break; 36 | } else { 37 | current = current->next_group; 38 | } 39 | } 40 | return (string*)current; 41 | } 42 | 43 | // implementing two different ll-walking functions given the need to use 44 | // strcmp() for strings and strncmp() for groups felt easier than writing 45 | // a single one with a more complicated interface. 46 | string* walk_strings_ll(const string* const head, const char* const str) { 47 | const string* current = head; 48 | while (current != NULL) { 49 | if (strcmp(current->chars, str) == 0) { 50 | break; 51 | } else { 52 | current = current->next_string; 53 | } 54 | } 55 | return (string*)current; 56 | } 57 | -------------------------------------------------------------------------------- /ch-6/charmatch/src/types.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "charmatch.h" 6 | #include "common.h" 7 | 8 | // TODO: make sure we cover unions, enums, and possibly structs 9 | #define BASIC_COUNT 7 10 | static char* basic_c_types[] = {"char", "short", "int", "long", 11 | "float", "double", "bool"}; 12 | 13 | #define SPECIAL_COUNT 3 14 | static char* special_c_types[] = {"long", "signed", "unsigned"}; 15 | 16 | static type_name* defined_types = NULL; 17 | 18 | static int add_type(const char* const typename) { 19 | string* new_type = NULL; 20 | if ((new_type = alloc_string(typename)) != NULL) { 21 | new_type->next_string = defined_types; 22 | defined_types = new_type; 23 | } else { 24 | printf("add_type() | couldn't allocate a new type.\n"); 25 | return -1; 26 | } 27 | return 0; 28 | } 29 | 30 | // handle_define(): handle further parsing if "#define" is parsed. 31 | // Ex. "#define foobar int" 32 | int handle_define(void) { 33 | char space[MAXLEN]; 34 | char defn[MAXLEN]; 35 | char value[MAXLEN]; 36 | 37 | getword(space, MAXLEN); 38 | getword(defn, MAXLEN); 39 | getword(space, MAXLEN); 40 | getword(value, MAXLEN); 41 | 42 | if (istypename(value)) { 43 | return add_type(defn); 44 | } else { 45 | return 0; 46 | } 47 | } 48 | 49 | // handle_typedef(): handle further parsing if "typedef" is parsed 50 | void handle_typedef(void) {} 51 | 52 | // istypename(): given our lists above, determine if a parsed word is a type 53 | // name 54 | int istypename(const char* const word) { 55 | for (int i = 0; i < BASIC_COUNT; i++) { 56 | if (strcmp(word, basic_c_types[i]) == 0) { 57 | return 1; 58 | } 59 | } 60 | for (int i = 0; i < SPECIAL_COUNT; i++) { 61 | if (strcmp(word, special_c_types[i]) == 0) { 62 | return 1; 63 | } 64 | } 65 | type_name* current = defined_types; 66 | while (current != NULL) { 67 | if (strcmp(current->chars, word) == 0) { 68 | return 1; 69 | } 70 | current = current->next_string; 71 | } 72 | return 0; 73 | } 74 | 75 | void cleanup_typenames() {} 76 | -------------------------------------------------------------------------------- /ch-6/crossref/README.md: -------------------------------------------------------------------------------- 1 | crossref 2 | --- 3 | 4 | ### Exercise description 5 | Exercise 6-3. Write a cross-referencer that prints a list of all words in a 6 | document, and, for each word, a list of the line numbers on which it occurs. 7 | Remove noise words like "the," "and," and so on. 8 | 9 | ### Solution strategy 10 | We will keep a tree of words. Each node will contain an array of ints that 11 | will store the line numbers the word occurs on. We will dynamically resize 12 | these arrays as needed. We will read line by line from stdin, incrementing a 13 | linecount each time we see a \n. At the end, we will walk the tree and print 14 | the words and their counts. 15 | 16 | ### Note 17 | Solving this exercise prompted me to write the BST library in `common/`, so there is some code replication between the two. 18 | -------------------------------------------------------------------------------- /ch-6/crossref/crossref-test-full.txt: -------------------------------------------------------------------------------- 1 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 2 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 3 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 4 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 5 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 6 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 7 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 8 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 9 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 10 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 11 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 12 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 13 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 14 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 15 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 16 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 17 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 18 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 19 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 20 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 21 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 22 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 23 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 24 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 25 | aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz 26 | -------------------------------------------------------------------------------- /ch-6/crossref/crossref-test-part.txt: -------------------------------------------------------------------------------- 1 | dog 2 | cat 3 | fish 4 | fox 5 | bear 6 | dog fox 7 | cat bear 8 | fish cat 9 | fish bear 10 | dog fox 11 | -------------------------------------------------------------------------------- /ch-6/crossref/include/crossref.h: -------------------------------------------------------------------------------- 1 | #define MAX_WORD 100 2 | #define INIT_LINES 5 3 | 4 | typedef struct word_node { 5 | char* word; 6 | size_t* lines; 7 | size_t lines_n; 8 | size_t lines_max; 9 | struct word_node* left; 10 | struct word_node* right; 11 | } word_node; 12 | 13 | // io.c 14 | int getword(char* const word); 15 | 16 | // node.c 17 | word_node* create_node(const char* const word); 18 | int add_line(word_node* const node, const size_t line_no); 19 | void display_lines(const word_node* const node); 20 | 21 | // tree.c 22 | word_node* tree_insert(const char* const word, word_node* const node); 23 | word_node* tree_search(const char* const word, const word_node* const node); 24 | void tree_walk(const word_node* const head); 25 | void tree_cleanup(word_node* head); 26 | -------------------------------------------------------------------------------- /ch-6/crossref/src/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "crossref.h" 5 | 6 | /* 7 | static int is_noise_word( char* word, word_node* noise_words) { 8 | if (tree_search(word, noise_words) != NULL) { 9 | return 1; 10 | } else { 11 | return 0; 12 | } 13 | } 14 | */ 15 | 16 | // getword(): get the next word from input. Returns the last 17 | // char read, or EOF 18 | int getword(char* const word) { 19 | int i = 0; 20 | int c = 0; 21 | c = getchar(); 22 | while (i < MAX_WORD - 1 && isalnum(c) && c != EOF) { 23 | word[i] = (char)c; 24 | i++; 25 | c = getchar(); 26 | } 27 | word[i] = '\0'; 28 | return c; 29 | } 30 | -------------------------------------------------------------------------------- /ch-6/crossref/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "crossref.h" 8 | 9 | int main() { 10 | int c = 0; 11 | size_t line_count = 1; 12 | char word[MAX_WORD]; 13 | word_node* head = NULL; 14 | word_node* current = NULL; 15 | 16 | while ((c = getword(word)) != EOF) { 17 | if (strlen(word) > 0) { 18 | if (head == NULL) { 19 | head = create_node(word); 20 | } 21 | // see if we've encountered the word before 22 | if ((current = tree_search(word, head)) == NULL) { 23 | current = tree_insert(word, head); 24 | } 25 | 26 | // add this line to the word's list of lines 27 | if (add_line(current, line_count) == -1) { 28 | return -1; 29 | } 30 | } 31 | 32 | // bump line count if we are going to the next line 33 | if (c == '\n') { 34 | line_count++; 35 | } 36 | } 37 | 38 | // display tree contents, then cleanup and exit 39 | if (head != NULL) { 40 | tree_walk(head); 41 | tree_cleanup(head); 42 | } 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /ch-6/crossref/src/tree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma clang diagnostic ignored "-Wcast-qual" 6 | 7 | #include "crossref.h" 8 | 9 | // tree_insert(): insert word in tree 10 | word_node* tree_insert(const char* const word, word_node* const node) { 11 | if (node == NULL) { 12 | return NULL; 13 | } 14 | 15 | word_node* ret_node = NULL; 16 | int result = strcmp(node->word, word); 17 | // Word is already present in tree 18 | if (result == 0) { 19 | ret_node = node; 20 | } // left subtree 21 | else if (result < 0) { 22 | if (node->left == NULL) { 23 | node->left = create_node(word); 24 | ret_node = node->left; 25 | } else { 26 | ret_node = tree_insert(word, node->left); 27 | } 28 | } // right subtree 29 | else { 30 | if (node->right == NULL) { 31 | node->right = create_node(word); 32 | ret_node = node->right; 33 | } else { 34 | ret_node = tree_insert(word, node->right); 35 | } 36 | } 37 | return ret_node; 38 | } 39 | 40 | // tree_search(): return node in tree storing word, or NULL 41 | word_node* tree_search(const char* const word, const word_node* const node) { 42 | if (node == NULL) { 43 | return NULL; 44 | } 45 | 46 | // walk tree based on comparison to word 47 | int result = strcmp(node->word, word); 48 | if (result == 0) { 49 | return (word_node*)node; 50 | } else if (result < 0) { 51 | return tree_search(word, node->left); 52 | } else { 53 | return tree_search(word, node->right); 54 | } 55 | } 56 | 57 | // tree_walk(): post-order walk the tree, printing strings and line buffers 58 | void tree_walk(const word_node* const head) { 59 | if (head == NULL) { 60 | return; 61 | } 62 | tree_walk(head->right); 63 | display_lines(head); 64 | tree_walk(head->left); 65 | } 66 | 67 | // tree_cleanup(): recursively free each node in the tree 68 | void tree_cleanup(word_node* node) { 69 | if (node->left != NULL) { 70 | tree_cleanup(node->left); 71 | } 72 | if (node->right != NULL) { 73 | tree_cleanup(node->right); 74 | } 75 | free(node->word); 76 | free(node->lines); 77 | free(node); 78 | } 79 | -------------------------------------------------------------------------------- /ch-6/hashtable/README.md: -------------------------------------------------------------------------------- 1 | hashtable 2 | --- 3 | 4 | ### Problem statements 5 | Exercise 6-5: Write a function `undef` that will remove a name and definition from the table maintained by lookup and install. 6 | 7 | Exercise 6-6: Implement a simple version of the #define processor (i.e. no arguments) suitable for use with C programs, based on the routines of this section. You may also find `getch` and `ungetch` helpful. 8 | 9 | ### Strategies 10 | 6-5: `undef` need only engage in linked list deletion - it can do a lookup, match a kv mapping, and then set prev's next to next->next, then free the current node. In conjunction with 6.6, we can implement the `#undef` macro. 11 | 12 | 6-6: The `#define` processor can just read words from stdin; if `#define` is encountered, the next word is the key, and the following word is the value. Then, hash each following word - if we get a match, emit to stdout the value, otherwise just emit the word. 13 | -------------------------------------------------------------------------------- /ch-6/hashtable/hashtable-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define test1 100 4 | #define test2 200 5 | #define test3 300 6 | #define foobar baz 7 | 8 | int main() { 9 | int baz = 5; 10 | printf("%d\n", foobar + baz); 11 | #undef foobar 12 | int foobar = 110; 13 | return foobar; 14 | } 15 | -------------------------------------------------------------------------------- /ch-6/hashtable/include/hashtable.h: -------------------------------------------------------------------------------- 1 | #define HASHSIZE 101 2 | 3 | // table entry 4 | typedef struct kv { 5 | struct kv* next; // next entry in chain 6 | char* key; // defined name 7 | char* value; // replacement text 8 | } kv; 9 | 10 | extern kv* hashtab[HASHSIZE]; 11 | 12 | // hashtable 13 | size_t hash(const char* const str); 14 | kv* lookup(const char* const str); 15 | kv* install(const char* const name, const char* const defn); 16 | void uninstall(const char* const key); 17 | 18 | // preprocessor 19 | int define(const char* const word); 20 | int undef(const char* const word); 21 | 22 | // mem 23 | kv* free_entry(kv* entry); 24 | kv* alloc_entry(const char* const key, const char* const value); 25 | void free_hashtable(void); 26 | -------------------------------------------------------------------------------- /ch-6/hashtable/src/hashtable.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "hashtable.h" 7 | 8 | // hash(): form hash value for string s 9 | size_t hash(const char* const key) { 10 | const char* s = key; 11 | size_t hashval; 12 | 13 | for (hashval = 0; *s != '\0'; s++) { 14 | hashval += (size_t)(*s) + 31 * hashval; 15 | } 16 | 17 | return hashval % HASHSIZE; 18 | } 19 | 20 | kv* lookup(const char* const key) { 21 | kv* entry; 22 | for (entry = hashtab[hash(key)]; entry != NULL; entry = entry->next) { 23 | if (strcmp(key, entry->key) == 0) { 24 | return entry; // found 25 | } 26 | } 27 | return NULL; // not found 28 | } 29 | 30 | kv* install(const char* const key, const char* const value) { 31 | kv* entry; 32 | size_t hashval; 33 | 34 | if ((entry = lookup(key)) == NULL) { 35 | // new entry 36 | if ((entry = alloc_entry(key, value)) != NULL) { 37 | hashval = hash(key); 38 | entry->next = hashtab[hashval]; 39 | hashtab[hashval] = entry; 40 | } else { 41 | // couldn't allocate 42 | return NULL; 43 | } 44 | } else { 45 | // already there 46 | free((void*)entry->value); 47 | if ((entry->value = strdup(value)) == NULL) { 48 | return NULL; 49 | } 50 | } 51 | return entry; 52 | } 53 | 54 | void uninstall(const char* const key) { 55 | kv* entry = NULL; 56 | kv* next = NULL; 57 | if ((entry = lookup(key)) != NULL) { 58 | while (entry->next != NULL) { 59 | if (strcmp(key, entry->next->key) == 0) { 60 | next = free_entry(entry); 61 | entry->next = next; 62 | } else { 63 | entry = entry->next; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ch-6/hashtable/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "common.h" 5 | #include "hashtable.h" 6 | 7 | kv* hashtab[HASHSIZE]; 8 | 9 | int main() { 10 | int c = 0; 11 | char word[MAXLEN] = {0}; 12 | while ((c = getword(word, MAXLEN)) > 0) { 13 | if (strcmp(word, "#define") == 0) { 14 | define(word); 15 | } else if (strcmp(word, "#undef") == 0) { 16 | undef(word); 17 | } else { 18 | // lookup and print val or word 19 | kv* entry; 20 | if ((entry = lookup(word)) != NULL) { 21 | printf("%s", entry->value); 22 | } else { 23 | printf("%s", word); 24 | } 25 | } 26 | } 27 | 28 | free_hashtable(); 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /ch-6/hashtable/src/mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "hashtable.h" 5 | 6 | // free_entry(): handles de-allocation for a hashtable entry. 7 | // Returns a pointer to the next entry in the linked list, or null if there 8 | // isn't one. 9 | 10 | void free_hashtable() { 11 | kv* entry; 12 | 13 | for (int i = 0; i < HASHSIZE; i++) { 14 | while (hashtab[i] != NULL) { 15 | entry = hashtab[i]; 16 | hashtab[i] = entry->next; 17 | free_entry(entry); 18 | } 19 | } 20 | } 21 | 22 | kv* free_entry(kv* entry) { 23 | if (entry == NULL) { 24 | return NULL; 25 | } 26 | kv* next = entry->next; 27 | free(entry->key); 28 | free(entry->value); 29 | free(entry); 30 | return next; 31 | } 32 | 33 | kv* alloc_entry(const char* const key, const char* const value) { 34 | kv* entry; 35 | 36 | if ((entry = (kv*)malloc(sizeof(kv))) == NULL) { 37 | return NULL; 38 | } 39 | 40 | if ((entry->key = strdup(key)) == NULL) { 41 | free(entry); 42 | return NULL; 43 | } 44 | 45 | if ((entry->value = strdup(value)) == NULL) { 46 | free(entry->key); 47 | free(entry); 48 | return NULL; 49 | } 50 | 51 | entry->next = NULL; 52 | return entry; 53 | } 54 | -------------------------------------------------------------------------------- /ch-6/hashtable/src/preprocessor.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "hashtable.h" 5 | 6 | // define(): read the two following words, and use them 7 | // for installation in the hashtable. 8 | int define(const char* const word) { 9 | int c1 = 0; 10 | int c2 = 0; 11 | char key[MAXLEN] = {0}; 12 | char value[MAXLEN] = {0}; 13 | 14 | printf("%s", word); 15 | printf("%c", getchar()); // skip whitespace between "#define" and key 16 | c1 = getword(key, MAXLEN); 17 | printf("%s", key); 18 | printf("%c", getchar()); // skip whitespace between key and value 19 | c2 = getword(value, MAXLEN); 20 | printf("%s", value); 21 | 22 | if (c1 != EOF || c2 != EOF) { 23 | install(key, value); 24 | } 25 | return 0; 26 | } 27 | 28 | // undefine(): read the next word after 29 | int undef(const char* const word) { 30 | int c; 31 | char key[MAXLEN] = {0}; 32 | 33 | printf("%s", word); 34 | printf("%c", getchar()); // skip whitespace between "#undef" and key 35 | c = getword(key, MAXLEN); 36 | printf("%s", key); 37 | 38 | if (c != EOF) { 39 | uninstall(key); 40 | } 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /ch-6/kwcount/kwcount-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(){ 4 | printf("Hello, world!"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /ch-6/wordcount/README.md: -------------------------------------------------------------------------------- 1 | wordcount 2 | ---- 3 | 4 | ### Problem statement 5 | 6-4: Write a program that prints the distinct words in its input sorted into 6 | decreasing order of frequency of occurrence. Precede each word by its count. 7 | 8 | ### Strategy 9 | First, we need to go through the program and count words. We can use a tree 10 | for storing words and retrieving them efficiently, borrowing from crossref. 11 | The ordering of the tree will be by words (i.e. strcmp() results) and each node 12 | will also store the number of occurences. 13 | 14 | Second, we can walk the first tree, and create a second tree - this one 15 | will be ordered by the number of occurrences, and each node will contain words. 16 | Left nodes are <=, right nodes are >. 17 | 18 | Third, we walk the second tree pre-order - this will print the nodes in order. 19 | -------------------------------------------------------------------------------- /ch-6/wordcount/include/wordcount.h: -------------------------------------------------------------------------------- 1 | #include "trees.h" 2 | 3 | typedef struct word_count { 4 | char* word; 5 | size_t count; 6 | } word_count; 7 | 8 | extern tnode* word_tree; 9 | extern tnode* count_tree; 10 | 11 | // mem.c 12 | word_count* word_count_alloc(const char* const word); 13 | void word_count_free(void* wc); 14 | 15 | // compare.c 16 | int count_cmp(const void* const node1, const void* const node2); 17 | int word_cmp(const void* const node1, const void* const node2); 18 | 19 | // trees.c 20 | void create_count_tree(const tnode* const word_tree_head); 21 | tnode* create_word_tree(tnode* tree); 22 | 23 | // display.c 24 | void word_count_extractor(const tnode* const node); 25 | void result_print(const tnode* const node); 26 | -------------------------------------------------------------------------------- /ch-6/wordcount/src/compare.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "wordcount.h" 5 | 6 | int count_cmp(const void* const word_count1, const void* const word_count2) { 7 | if (word_count1 == NULL) { 8 | printf("word_cmp() | error - word_count1 is NULL.\n"); 9 | return -1; 10 | } else if (word_count2 == NULL) { 11 | printf("word_cmp() | error - word_count2 is NULL.\n"); 12 | return -1; 13 | } 14 | const word_count* const wc1 = (const word_count*)(word_count1); 15 | const word_count* const wc2 = (const word_count*)(word_count2); 16 | 17 | if (wc1->count == wc2->count) { 18 | return 0; 19 | } else if (wc1->count < wc2->count) { 20 | return -1; 21 | } else { 22 | return 1; 23 | } 24 | } 25 | 26 | int word_cmp(const void* const word_count1, const void* const word_count2) { 27 | if (word_count1 == NULL) { 28 | printf("word_cmp() | error - word_count1 is NULL.\n"); 29 | return -1; 30 | } else if (word_count2 == NULL) { 31 | printf("word_cmp() | error - word_count2 is NULL.\n"); 32 | return -1; 33 | } 34 | const word_count* const wc1 = (const word_count*)(word_count1); 35 | const word_count* const wc2 = (const word_count*)(word_count2); 36 | 37 | if (wc1->word == NULL) { 38 | printf("word_cmp() | error - node1->word_count->word is NULL.\n"); 39 | return -1; 40 | } else if (wc2->word == NULL) { 41 | printf("word_cmp() | error - node1->word_count->word is NULL.\n"); 42 | return -1; 43 | } 44 | 45 | // In our BST, we treat "coming after" alphabetically as "greater than"; 46 | // if we insert "cat", "bat", and "hat", the result should be that "cat" is 47 | // the root, "bat" is its left child, and "hat" is its right child. However, 48 | // strcmp("cat", "hat") will return a negative because "hat" comes after 49 | // "cat", and this will cause the tree to make "hat" the left child of "bat" 50 | // instead, so we need to multiply the result by -1. 51 | return strcmp(wc1->word, wc2->word) * -1; 52 | } 53 | -------------------------------------------------------------------------------- /ch-6/wordcount/src/display.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "wordcount.h" 4 | 5 | void word_count_extractor(const tnode* const node) { 6 | // deep copy node data 7 | char* word = ((word_count*)(node->data))->word; 8 | size_t count = ((word_count*)(node->data))->count; 9 | word_count* wc = word_count_alloc(word); 10 | wc->count = count; 11 | 12 | // insert node data 13 | if (count_tree == NULL) { 14 | count_tree = tree_insert(count_tree, wc, sizeof(word_count), count_cmp); 15 | } else { 16 | tree_insert(count_tree, wc, sizeof(word_count), count_cmp); 17 | } 18 | } 19 | 20 | void result_print(const tnode* const node) { 21 | word_count* data = (word_count*)node->data; 22 | printf("%s: %lu\n", data->word, data->count); 23 | } 24 | -------------------------------------------------------------------------------- /ch-6/wordcount/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "common.h" 5 | #include "trees.h" 6 | #include "wordcount.h" 7 | 8 | tnode* word_tree = NULL; 9 | tnode* count_tree = NULL; 10 | 11 | int main() { 12 | word_tree = create_word_tree(word_tree); 13 | create_count_tree(word_tree); 14 | trav_inorder(count_tree, result_print); 15 | tree_cleanup(word_tree, word_count_free); 16 | tree_cleanup(count_tree, word_count_free); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /ch-6/wordcount/src/mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "wordcount.h" 6 | 7 | word_count* word_count_alloc(const char* const word) { 8 | word_count* wc = NULL; 9 | size_t len = strlen(word); 10 | 11 | wc = malloc(sizeof(word_count)); 12 | if (wc == NULL) { 13 | return NULL; 14 | } 15 | 16 | wc->word = malloc(len * sizeof(char) + 1); 17 | if (wc->word == NULL) { 18 | free(wc); 19 | return NULL; 20 | } 21 | 22 | strcpy(wc->word, word); 23 | wc->count = 1; 24 | return wc; 25 | } 26 | 27 | void word_count_free(void* wc) { 28 | if (wc == NULL) { 29 | printf("word_count_free() | wc is null - nothing to free.\n"); 30 | return; 31 | } 32 | 33 | word_count* word_c = (word_count*)wc; 34 | if (word_c->word == NULL) { 35 | printf("word_count_free() | error - no word to free.\n"); 36 | return; 37 | } 38 | // printf("word_count_free() | freeing %s\n", word_c->word); 39 | free(word_c->word); 40 | free(word_c); 41 | } 42 | -------------------------------------------------------------------------------- /ch-6/wordcount/src/tree.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "wordcount.h" 5 | 6 | void create_count_tree(const tnode* const word_tree_head) { 7 | trav_inorder(word_tree_head, word_count_extractor); 8 | } 9 | 10 | tnode* create_word_tree(tnode* tree) { 11 | int i; 12 | char word[MAXLEN] = {0}; 13 | tnode* current = NULL; 14 | word_count* wdc; 15 | 16 | while ((i = getword(word, MAXLEN)) > 0) { 17 | if (tree == NULL) { 18 | wdc = word_count_alloc(word); 19 | tree = tree_insert(tree, wdc, sizeof(word_count), 20 | word_cmp); // as long as i > 0, cast to size_t is ok 21 | continue; 22 | } 23 | 24 | // Increment word counter if in tree, add word 25 | // if not. 26 | word_count temp; 27 | temp.word = word; 28 | if ((current = tree_search(tree, &temp, word_cmp)) != NULL) { 29 | ((word_count*)(current->data))->count++; 30 | } else { 31 | wdc = word_count_alloc(word); 32 | tree_insert(tree, wdc, sizeof(word_count), word_cmp); 33 | } 34 | } 35 | 36 | return tree; 37 | } 38 | -------------------------------------------------------------------------------- /ch-6/wordcount/wordcount-test-1.txt: -------------------------------------------------------------------------------- 1 | The quick brown fox jumped over the lazy dog 2 | In the beginning God created the heavens and the earth 3 | Call me ishmael 4 | This is the song that never ends 5 | ishmael ishmael ishmael that that 6 | -------------------------------------------------------------------------------- /ch-6/wordcount/wordcount-test.txt: -------------------------------------------------------------------------------- 1 | mm aa nn cc zz 2 | aa qq rr zz mm nn 3 | aa 4 | nn aa aa 5 | -------------------------------------------------------------------------------- /ch-7/Makefile: -------------------------------------------------------------------------------- 1 | 7.1: 7.1-build 2 | ifdef RUN_TESTS 3 | $(VALGRIND) ./bin/$@ -u "this is a test string" 4 | $(VALGRIND) ./bin/$@ -l "THIS IS A TEST STRING" 5 | endif 6 | 7 | 7.2: 7.2-build 8 | ifdef RUN_TESTS 9 | printf "0xff\n" | $(VALGRIND) ./bin/$@ 10 | printf "\037" | $(VALGRIND) ./bin/$@ 11 | endif 12 | 13 | 7.3: 7.3-build 7.3-basic-test 14 | 15 | 7.4: 7.4-build 16 | ifdef RUN_TESTS 17 | printf "5 - 4.0 - foobar\n" | $(VALGRIND) ./bin/$@ 18 | printf "300 + 5" | $(VALGRIND) ./bin/$@ 19 | endif 20 | 21 | 7.5: 7.5-build 22 | ifdef RUN_TESTS 23 | echo "300 5 +" | $(VALGRIND) ./bin/$@ 24 | echo "300 5 + 200 5 - +" | $(VALGRIND) ./bin/$@ 25 | echo "1 5 *" | $(VALGRIND) ./bin/$@ 26 | echo "10 5 /" | $(VALGRIND) ./bin/$@ 27 | -echo "10 0 /" | $(VALGRIND) ./bin/$@ 28 | endif 29 | 30 | 7.6: 7.6-build 31 | ifdef RUN_TESTS 32 | printf "a\nb\nc\nd\ne" > ./bin/7.6-test-1.txt 33 | printf "a\nb\nc\nd\ne" > ./bin/7.6-test-2.txt 34 | $(VALGRIND) ./bin/$@ ./bin/7.6-test-2.txt ./bin/7.6-test-1.txt 35 | echo "foobar" >> ./bin/7.6-test-1.txt 36 | echo "foobaz" >> ./bin/7.6-test-2.txt 37 | $(VALGRIND) ./bin/$@ ./bin/7.6-test-2.txt ./bin/7.6-test-1.txt 38 | endif 39 | 40 | 7.7: 7.7-build 41 | ifdef RUN_TESTS 42 | printf "match-on-this" > ./bin/7.7-pattern.txt 43 | printf "match-on-this" | $(VALGRIND) ./bin/$@ -f ./bin/7.7-pattern.txt 44 | printf "match because x" | $(VALGRIND) ./bin/$@ -x -f ./bin/7.7-pattern.txt 45 | printf "no match" | $(VALGRIND) ./bin/$@ -f ./bin/7.7-pattern.txt 46 | endif 47 | 48 | 7.8: 7.8-build 49 | ifdef RUN_TESTS 50 | for LINE_NO in `seq 1 120`; do \ 51 | echo "$$LINE_NO: ABCDEFGHIJKLMNOPQRSTUVWXYZ" >> ./bin/7.8-test.txt; \ 52 | done 53 | $(VALGRIND) ./bin/$@ ./bin/7.8-test.txt 54 | endif 55 | 56 | 7.9: 7.9-build 7.9-basic-test 57 | -------------------------------------------------------------------------------- /ch-7/README.md: -------------------------------------------------------------------------------- 1 | Ch-7 2 | --- 3 | 4 | ## Notes about the exercises 5 | * 7.3: 6 | * _Technically_ is done since all I did was make it possible to print hex / octal values, but I'd like to return to it and see if I can get min / max width working too. 7 | -------------------------------------------------------------------------------- /ch-7/src/7.1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* Write a program that converts upper case to lower 8 | * or lower case to upper, depending on the name it 9 | * is invoked with, as found in argv[0]. 10 | * --- 11 | * If there's only 2 args (i.e. piped), get input from stdin 12 | */ 13 | 14 | // read_stdin(): helper function if program 15 | // is called with piped input. Will not 16 | // work if input is too large. 17 | static char* read_stdin(void) { 18 | int i = 0; 19 | int c = 0; 20 | char* string = (char*)calloc(1, 1000); 21 | while ((c = getchar()) != EOF) { 22 | string[i++] = (char)c; 23 | } 24 | return string; 25 | } 26 | 27 | // main(): solves exercise 28 | int main(int argc, char** argv) { 29 | char* case_flag = NULL; 30 | char* in_string = NULL; 31 | bool alloc = false; 32 | 33 | // set vars based on cli args; 34 | // read from stdin if we are piping 35 | switch (argc) { 36 | case 2: 37 | alloc = true; 38 | in_string = read_stdin(); 39 | case_flag = argv[1]; 40 | break; 41 | case 3: 42 | case_flag = argv[1]; 43 | in_string = argv[2]; 44 | break; 45 | default: 46 | printf("usage: caser -[ul] \n"); 47 | return -1; 48 | } 49 | 50 | // Transform string based on args 51 | if (strcmp("-u", case_flag) == 0) { 52 | for (size_t i = 0; i < strlen(in_string); i++) { 53 | putchar(toupper(in_string[i])); 54 | } 55 | } else if (strcmp("-l", case_flag) == 0) { 56 | for (size_t i = 0; i < strlen(in_string); i++) { 57 | putchar(tolower(in_string[i])); 58 | } 59 | } else { 60 | if (alloc) { 61 | free(in_string); 62 | } 63 | printf("usage: caser -[ul] \n"); 64 | return -1; 65 | } 66 | putchar('\n'); 67 | 68 | if (alloc) { 69 | free(in_string); 70 | } 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /ch-7/src/7.2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * 7.2: Write a program that will print arbitrary input in a sensible way. As a 6 | * minimum, it should print non-graphic characters in octal or hexadecimal 7 | * according to local custom, and break long text lines. 8 | * --- 9 | * I was not sure what this question was asking, but after checking out two 10 | * solutions, I think the question is "print non-graphical characters in hex or 11 | * octal, and print everything else not-insanely". Nongraphical characters can 12 | * be found using iscntrl() in ctype.h, which will also catch blanks, the delete 13 | * character, and any chars less than octal 040. 14 | * */ 15 | 16 | int main() { 17 | int i = 1; 18 | int c = 0; 19 | while ((c = getchar()) != EOF) { 20 | if (iscntrl(c) && c != ' ' && c != '\n') { 21 | printf("Ox%x ", c); 22 | i += 7; // advance to compensate for "0xFFFF " at most. 23 | } else { 24 | printf("%c", c); 25 | i++; 26 | } 27 | 28 | // break if line is larger than 80 chars 29 | if (i > 80) { 30 | putchar('\n'); 31 | i = 0; 32 | } 33 | } 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /ch-7/src/7.3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma clang diagnostic ignored "-Wcast-qual" 5 | 6 | /* 7 | * Revise minprintf to handle more of the other facilities of printf() 8 | */ 9 | 10 | // minprintf(): minimal printf with variable argument list 11 | static void minprintf(const char* const fmt, ...) { 12 | // va_list points to each unnamed arg in turn 13 | va_list ap; 14 | char *p, *sval; 15 | int ival; 16 | double dval; 17 | 18 | // make ap point to first unnamed arg 19 | va_start(ap, fmt); 20 | for (p = (char*)fmt; *p; p++) { 21 | if (*p != '%') { 22 | putchar(*p); 23 | continue; 24 | } 25 | switch (*++p) { 26 | case 'd': 27 | ival = va_arg(ap, int); 28 | printf("%d", ival); 29 | break; 30 | case 'f': 31 | dval = va_arg(ap, double); 32 | printf("%f", dval); 33 | break; 34 | case 's': 35 | for (sval = va_arg(ap, char*); *sval; sval++) { 36 | putchar(*sval); 37 | } 38 | break; 39 | case 'o': 40 | ival = va_arg(ap, int); 41 | printf("%o", ival); 42 | break; 43 | case 'x': 44 | ival = va_arg(ap, int); 45 | printf("%x", ival); 46 | break; 47 | default: 48 | putchar(*p); 49 | break; 50 | } 51 | } 52 | va_end(ap); // clean up when done 53 | } 54 | 55 | int main() { 56 | minprintf("%d %f %s %o %x\n", 10, 20.5, "foobarbaz", 999, 255); 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /ch-7/src/7.6.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * Ex. 7.6: Write a program to compare two files, printing the first line where 6 | * they differ 7 | */ 8 | 9 | #define READ_SIZE 100 10 | 11 | int main(int argc, char** argv) { 12 | if (argc != 3) { 13 | printf("usage: ./7.6 \n"); 14 | return -1; 15 | } 16 | 17 | // Open files for reading 18 | FILE* f1 = NULL; 19 | FILE* f2 = NULL; 20 | if ((f1 = fopen(argv[1], "r")) == NULL) { 21 | printf("Cannot open %s.\n", argv[0]); 22 | perror(""); 23 | return -1; 24 | } 25 | 26 | if ((f2 = fopen(argv[2], "r")) == NULL) { 27 | printf("Cannot open %s.\n", argv[1]); 28 | perror(""); 29 | return -1; 30 | } 31 | 32 | // continuously call fgets() on both files 33 | // and strcmp() the results. They are only 34 | // the same if strcmp() never returns nonzero 35 | // and they both EOF simultaneously. 36 | char read1[READ_SIZE] = {0}; 37 | char read2[READ_SIZE] = {0}; 38 | char* ret1; 39 | char* ret2; 40 | int cmp = 0; 41 | int line = 0; 42 | do { 43 | ret1 = fgets(read1, READ_SIZE, f1); 44 | ret2 = fgets(read2, READ_SIZE, f2); 45 | cmp = strcmp(read1, read2); 46 | line++; 47 | } while (cmp == 0 && !(ret1 == NULL || ret2 == NULL)); 48 | 49 | // Determine result based on exit condtions 50 | if (ret1 != NULL && ret2 == NULL) { 51 | printf("%s is longer than %s\n", argv[1], argv[2]); 52 | } else if (ret1 == NULL && ret2 != NULL) { 53 | printf("%s is longer than %s\n", argv[2], argv[1]); 54 | } else if (cmp != 0) { 55 | printf("Files differ on line %d:\n%s: %s%s: %s", line, argv[1], read1, 56 | argv[2], read2); 57 | } else { 58 | printf("Files %s and %s are the same.\n", argv[1], argv[2]); 59 | } 60 | 61 | fclose(f1); 62 | fclose(f2); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /ch-7/src/7.8.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Exercise 7-8. Write a program to print a set of files, starting each new one 5 | * on a new page, with a title and a running page count for each file. 6 | */ 7 | 8 | #define PAGE_LEN 40 9 | #define READ_SIZE 100 10 | 11 | static int paged_print(const char* const path) { 12 | FILE* file; 13 | if ((file = fopen(path, "r")) == NULL) { 14 | printf("Cannot open %s.\n", path); 15 | perror(""); 16 | return -1; 17 | } 18 | 19 | char line[READ_SIZE]; 20 | int line_count = 0; 21 | int page_count = 1; 22 | while (fgets(line, READ_SIZE, file) != NULL) { 23 | if (line_count % PAGE_LEN == 0) { 24 | // print title and page number 25 | printf("\n%s\nPage: %d\n", path, page_count++); 26 | } 27 | printf("%s", line); 28 | line_count++; 29 | } 30 | if (fclose(file) == -1) { 31 | printf("Cannot close %s.\n", path); 32 | perror(""); 33 | return -1; 34 | } 35 | return 0; 36 | } 37 | 38 | int main(int argc, char** argv) { 39 | for (int i = 1; i < argc; i++) { 40 | if (paged_print(argv[i]) == -1) { 41 | return -1; 42 | } 43 | } 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /ch-7/src/7.9.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Exercise 7.9: Functions like isupper can be implemented to save space or save 5 | * time. Explore both possibilities. 6 | * --- 7 | * I'm actually not sure why this could be space or time efficient. My 8 | * `isupper_best`, while specific to ASCII, is O(1) on time and space. 9 | * --- 10 | * Some interesting discussion here: 11 | * http://clc-wiki.net/wiki/K%26R2_solutions:Chapter_7:Exercise_9 12 | */ 13 | 14 | static int isupper_best(const char c) { return 'A' <= c && c <= 'Z'; } 15 | 16 | int main() { 17 | printf("%c: %d\n", 'c', isupper_best('c')); 18 | printf("%c: %d\n", 'A', isupper_best('A')); 19 | printf("%c: %d\n", 'D', isupper_best('D')); 20 | printf("%c: %d\n", 'Z', isupper_best('Z')); 21 | printf("%c: %d\n", '1', isupper_best('1')); 22 | printf("%c: %d\n", ' ', isupper_best(' ')); 23 | } 24 | -------------------------------------------------------------------------------- /ch-8/Makefile: -------------------------------------------------------------------------------- 1 | # cat is 8.1 2 | cat: clean 3 | echo "foo's conents" > ./bin/foo.test 4 | echo "bar's contents" > ./bin/bar.test 5 | echo "baz's contents" > ./bin/baz.test 6 | $(COMPILE) -I ch-8/include/ ch-8/src/8.1.c -o bin/cat 7 | $(VALGRIND) ./bin/cat ./bin/foo.test ./bin/bar.test ./bin/baz.test 8 | rm ./bin/*.test 9 | 10 | 8.5: 8.5-build 11 | ifdef RUN_TESTS 12 | $(VALGRIND) ./bin/$@ ch-8 13 | endif 14 | 15 | # fopen (and getc) is 8.2 through 8.4 16 | # malloc is 8.6 through 8.8 17 | fopen_j malloc_j: 18 | $(COMPILE) -I ch-8/$@/include/ ch-8/$@/src/*.c -o bin/$@ 19 | $(VALGRIND) ./bin/$@ 20 | -------------------------------------------------------------------------------- /ch-8/fopen_j/include/fopen_j.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define BUFF_SIZE 10 5 | 6 | typedef struct _flags { 7 | bool _READ; 8 | bool _WRITE; 9 | bool _UNBUF; 10 | bool _EOF; 11 | bool _ERR; 12 | char _align[3]; // do not use, aligns struct to 8B 13 | } _flags; 14 | 15 | typedef struct _iobuf { 16 | int _align; // (4B) do not use 17 | int fd; // (4B) file descriptor 18 | char *buff; // (8B) pointer to I/O buffer 19 | char *ptr; // (8B) next character position within I/O buffer 20 | _flags flags; // (8B) 21 | } FILE_J; 22 | 23 | /* 24 | #define feof(p) (((p)->flag & _EOF) != 0) 25 | #define ferror(p) (((p)->flag & _ERR) != 0) 26 | #define fileno(p) ((p)->fd) 27 | #define getchar() getc(stdin) 28 | #define putchar(x) putc((x), stdout) 29 | */ 30 | 31 | // fopen.c 32 | FILE_J *fopen_j(const char *const name, const char *const mode); 33 | 34 | // fclose.c 35 | int fclose_j(FILE_J *file); 36 | 37 | // fseek.c 38 | int fseek_j(FILE_J *const fp, const long offset, const int whence); 39 | 40 | // buffering.c 41 | void print_buffer(const FILE_J *const fp); 42 | int fflush_j(FILE_J *const fp); 43 | int _fillbuff(FILE_J *const fp); 44 | int _flushbuff(FILE_J *const fp); 45 | 46 | // io.c 47 | int getc_j(FILE_J *const fp); 48 | int putc_j(FILE_J *const fp, const int character); 49 | -------------------------------------------------------------------------------- /ch-8/fopen_j/src/buffering.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "fopen_j.h" 9 | 10 | // print_buffer(): displays contents of buffer, useful for debugging. 11 | void print_buffer(const FILE_J* const fp) { 12 | printf("print_buffer() | Buffer contents:\n"); 13 | for (int i = 0; i < BUFF_SIZE; i++) { 14 | printf("%c", fp->buff[i]); 15 | } 16 | printf("\n----\n"); 17 | } 18 | 19 | // fflush_j(): empty a FILE_J's buffer to its file descriptor, and correct 20 | // the position. Can be called at will / arbitrarily many times. 21 | int fflush_j(FILE_J* const fp) { 22 | int flush, fill; 23 | flush = _flushbuff(fp); 24 | fill = _fillbuff(fp); 25 | return flush && fill; 26 | } 27 | 28 | //_fillbuff(): refill the I/O buffer with characters from the file 29 | int _fillbuff(FILE_J* const fp) { 30 | int count; 31 | count = (int)read(fp->fd, fp->buff, BUFF_SIZE); 32 | if (count == -1) { 33 | perror("_fillbuff: "); 34 | fp->flags._ERR = true; 35 | return -1; 36 | } 37 | 38 | // Seek the fd back to original place; writes to buff 39 | // need to be written back to the file in the correct 40 | // place. 41 | off_t status; 42 | status = lseek(fp->fd, -count, SEEK_CUR); 43 | if (status == -1) { 44 | perror("_fillbuff: "); 45 | fp->flags._ERR = true; 46 | return -1; 47 | } 48 | 49 | return 0; 50 | } 51 | 52 | // _flush_buff(): write fp's buffer to the file descriptor. 53 | int _flushbuff(FILE_J* const fp) { 54 | if (fp->flags._ERR) { 55 | printf("_flushbuff() | cannot write to file.\n"); 56 | return -1; 57 | } 58 | 59 | long written; 60 | written = write(fp->fd, fp->buff, (size_t)(fp->ptr - fp->buff)); 61 | if (written == -1) { 62 | perror("_flushbuff(): Couldn't flush buffer to file. "); 63 | fp->flags._ERR = true; 64 | return -1; 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /ch-8/fopen_j/src/fclose.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "fopen_j.h" 7 | 8 | // fclose_j(): close fd, and clean up associated memory for FILE_J 9 | int fclose_j(FILE_J* file) { 10 | if (_flushbuff(file) == -1) { 11 | printf("fclose_j() | couldn't flush buffer.\n"); 12 | return -1; 13 | } 14 | 15 | if (close(file->fd) == -1) { 16 | perror("fclose_j() | Couldn't close file: "); 17 | return -1; 18 | } 19 | 20 | free(file->buff); 21 | free(file); 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /ch-8/fopen_j/src/fseek.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fopen_j.h" 5 | 6 | // fseek(): flush the buffer contents, seek to the correct place, 7 | // refill the buffer, and correct the file position after the read. 8 | int fseek_j(FILE_J *const fp, const long offset, const int whence) { 9 | if (_flushbuff(fp) == -1) { 10 | fp->flags._ERR = true; 11 | return -1; 12 | } 13 | if (lseek(fp->fd, offset, whence) == -1) { 14 | fp->flags._ERR = true; 15 | return -1; 16 | } 17 | 18 | if (_fillbuff(fp) == -1) { 19 | fp->flags._ERR = true; 20 | return -1; 21 | } 22 | 23 | fp->ptr = fp->buff; 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /ch-8/fopen_j/src/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "fopen_j.h" 6 | 7 | // getc(): retrieve a single character from the file 8 | int getc_j(FILE_J* const fp) { 9 | int ret; 10 | // if read would go outside of the buffer, flush it, 11 | // and fill with the next region 12 | if (fp->ptr == fp->buff + BUFF_SIZE) { 13 | if (fflush_j(fp) == -1) { 14 | fp->flags._ERR = true; 15 | return EOF; 16 | } 17 | fp->ptr = fp->buff; 18 | } 19 | 20 | // Increment fp after read, then return 21 | ret = (int)*(fp->ptr); 22 | fp->ptr++; 23 | return ret; 24 | } 25 | 26 | // putc(): write a single char to a FILE_J 27 | int putc_j(FILE_J* const fp, const int character) { 28 | // same flushing behavior as getc_j() 29 | if (fp->ptr == fp->buff + BUFF_SIZE) { 30 | if (fflush_j(fp) == -1) { 31 | fp->flags._ERR = true; 32 | return EOF; 33 | } 34 | fp->ptr = fp->buff; 35 | } 36 | 37 | // Increment fp after write, then return 38 | *(fp->ptr) = (char)character; 39 | fp->ptr++; 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /ch-8/fopen_j/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "fopen_j.h" 10 | 11 | // debug_read(); reads len characters from file to str, 12 | // with useful segment for interrupting on a regular interval 13 | static void debug_read(FILE_J* file, char* string, int len) { 14 | for (int i = 0; i < len; i++) { 15 | string[i] = (char)getc_j(file); 16 | // if (!(i % 5)) { 17 | // printf("Halting til input at %lu.\n", lseek(file->fd, 0, SEEK_CUR)); 18 | // print_buffer(file); 19 | // getchar(); 20 | //} 21 | } 22 | } 23 | 24 | // debug_write(); writes string to file with useful 25 | // segment for interrupting on a regular interval 26 | static void debug_write(FILE_J* file, char* string) { 27 | for (int i = 0; i < (int)strlen(string); i++) { 28 | putc_j(file, string[i]); 29 | // if (!(i % 5)) { 30 | // printf("Halting til input at %lu.\n", lseek(file->fd, 0, SEEK_CUR)); 31 | // print_buffer(file); 32 | // getchar(); 33 | // } 34 | } 35 | } 36 | 37 | int main() { 38 | char* strings[] = {"abcdefghijklmnopqrstuvwxyz\0", 39 | "AAAAAAAAAAAAAAAAAAAAAAAAAA\0", 40 | "BBBBBBBBBBBBBBBBBBBBBBBBBB\0"}; 41 | char result[100] = {0}; 42 | 43 | // open the file 44 | char filename[] = "test.txt"; 45 | char mode = 'a'; 46 | FILE_J* file = fopen_j(filename, &mode); 47 | if (file == NULL) { 48 | printf("main() | File couldn't be opened.\n"); 49 | return -1; 50 | } 51 | 52 | // write first string, seek back, read, print 53 | debug_write(file, strings[0]); 54 | fseek_j(file, 0, SEEK_SET); 55 | debug_read(file, result, 26); 56 | printf("main() | Wrote %s to file.\nmain() | Read %s from file.\n\n", 57 | strings[0], result); 58 | 59 | // write second string at start, seek back to start, 60 | // and read it out again. Call flush frequently to 61 | // ensure it has no impact on normal program execution. 62 | fseek_j(file, 0, SEEK_SET); 63 | fflush_j(file); 64 | debug_write(file, strings[1]); 65 | fflush_j(file); 66 | fseek_j(file, 0, SEEK_SET); 67 | fflush_j(file); 68 | debug_read(file, result, 26); 69 | fflush_j(file); 70 | printf("main() | Wrote %s to file.\nmain() | Read %s from file.\n\n", 71 | strings[1], result); 72 | 73 | fclose_j(file); 74 | } 75 | -------------------------------------------------------------------------------- /ch-8/include/8.5.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef MAX_PATH 5 | #define MAX_PATH 255 6 | #endif 7 | 8 | void fsize(const char *const name); 9 | void dirwalk(const char *const name, void (*fcn)(const char *const)); 10 | -------------------------------------------------------------------------------- /ch-8/malloc_j/README.md: -------------------------------------------------------------------------------- 1 | malloc_j 2 | --- 3 | `malloc()` as implemented by Joshua. 4 | 5 | ## Notes 6 | Because the original K&R implementation uses `sbrk()` (which is considered obsolete), I have opted for a slightly different but common approach using `mmap()` instead, wherein I allocate a single large page at the beginning of program execution and pull from that via a custom `jbrk()` instead. 7 | 8 | ## Directions 9 | **Exercise 8-6**. The standard library function calloc(n,size) returns a pointer to n objects of size size, with the storage initialized to zero. Write calloc, by calling malloc or by modifying it. 10 | 11 | **Exercise 8-7**. malloc accepts a size request without checking its plausibility; free believes that the block it is asked to free contains a valid size field. Improve these routines so they take more pains with error checking. 12 | 13 | **Exercise 8-8**. Write a routine bfree(p,n) that will free an arbitrary block p of n characters into the free list maintained by malloc and free. By using b free, a user can add a static or external array to the free list at any time. 14 | 15 | ## Special thanks 16 | * [Elliott Jin (@robot-dreams)](https://github.com/robot-dreams) for some guidance on `sbrk()` use and strategy feedback. 17 | -------------------------------------------------------------------------------- /ch-8/malloc_j/include/malloc_j.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | 5 | // Misc 6 | #define MiB (1 << 20) 7 | 8 | typedef struct header { // block header 9 | size_t size; // size of block (4 bytes) 10 | struct header* next; // next block if on free list (8 bytes) 11 | } header; 12 | 13 | static const size_t unit_size = sizeof(header); 14 | 15 | // init.c 16 | extern header* free_list; // start of free list 17 | extern void* init_page; // base of allocated initial page used 18 | extern size_t total_units; 19 | 20 | int init(const size_t desired_page_size); 21 | int cleanup(void); 22 | header* initialize_new_chunk(void* const p, const size_t size); 23 | 24 | // malloc_j.c 25 | void* malloc_j(const size_t nbytes); 26 | void* calloc_j(const size_t size, const size_t count); 27 | 28 | // free_j.c 29 | int free_j(void* const chunk); 30 | int bfree(void* const p, const size_t n); 31 | -------------------------------------------------------------------------------- /ch-8/malloc_j/src/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "common.h" 6 | #include "malloc_j.h" 7 | 8 | header* free_list = NULL; // start of free list 9 | void* init_page = NULL; 10 | static size_t actual_page_size = 0; 11 | size_t total_units = 0; 12 | 13 | // init(): create first item in the free list by getting a massive page from 14 | // mmap() 15 | int init(const size_t desired_page_size) { 16 | actual_page_size = (desired_page_size < (unit_size * 2)) ? (unit_size * 2) 17 | : desired_page_size; 18 | 19 | if ((init_page = mmap(NULL, actual_page_size, PROT_READ | PROT_WRITE, 20 | MAP_SHARED | MAP_ANONYMOUS, -1, 0)) == MAP_FAILED) { 21 | char* err_desc = strerror(errno); 22 | printf("Couldn't allocate initial page: %s.\n", err_desc); 23 | return -1; 24 | } 25 | 26 | // Round the size of the initial chunk down to the nearest unit, remove one 27 | // unit for the header, and set the size in terms of units. Remainder bytes 28 | // are lost. 29 | free_list = initialize_new_chunk(init_page, actual_page_size); 30 | total_units = (actual_page_size / unit_size) - 1; 31 | printf("init() | init_page initialized.\n"); 32 | return 0; 33 | } 34 | 35 | int cleanup(void) { 36 | if (munmap(init_page, actual_page_size) == -1) { 37 | char* err_desc = strerror(errno); 38 | printf("Couldn't unmap initial page: %s.\n", err_desc); 39 | return -1; 40 | } 41 | printf("cleanup() | Unmapped initial page.\n"); 42 | return 0; 43 | } 44 | 45 | // initialize_new_chunk(): Set up an arbitrary blob of data for use in the free 46 | // list: 47 | // * Set the size to n, but: 48 | // * Remove one unit from size for the header. 49 | // * Round down to the nearest unit, discarding the 50 | // remainder. 51 | // * Initialize header's next pointer to itself. 52 | header* initialize_new_chunk(void* const p, const size_t size) { 53 | header* new_chunk = (header*)p; 54 | size_t usable_bytes = size - unit_size - (size % unit_size); 55 | new_chunk->size = usable_bytes / unit_size; 56 | new_chunk->next = new_chunk; 57 | return new_chunk; 58 | } 59 | -------------------------------------------------------------------------------- /ch-8/malloc_j/src/malloc_j.c: -------------------------------------------------------------------------------- 1 | #include "malloc_j.h" 2 | #include 3 | #include 4 | 5 | // malloc_j(): a general purpose storage allocator; returns 6 | // a pointer to a chunk of bytes usable for any purpose. 7 | void *malloc_j(const size_t bytes) { 8 | if (bytes == 0) { 9 | printf("malloc_j() | warning: must allocate at least 1B (0 requested).\n"); 10 | return NULL; 11 | } 12 | 13 | if (bytes > total_units) { 14 | printf( 15 | "malloc_j() | warning: %lu bytes exceeds max size allowed (%lu B).\n", 16 | bytes, total_units * unit_size); 17 | return NULL; 18 | } 19 | 20 | // determine how large an acceptable free chunk must be, in multiples of 21 | // unit_size; if we find one that's too big, we will need to split 22 | // it into two chunks, which must both be at least unit_size+1 in size. 23 | size_t units = ((bytes + unit_size - 1) / unit_size) + 1; 24 | 25 | // walk the list until either we come back to the start or we find a suitable 26 | // block 27 | header *p, *prev_node; 28 | for (prev_node = free_list, p = free_list->next; units > p->size; 29 | prev_node = p, p = p->next) { 30 | if (p == free_list && p <= prev_node) { // <= catches 1-node list 31 | printf("malloc_j() | error: no suitable blocks in free list.\n"); 32 | return NULL; 33 | } 34 | } 35 | 36 | // p->size is too big; create a new chunk from the tail 37 | // of p and return that 38 | if (p->size > units) { 39 | // allocate the tail end 40 | p->size = units; 41 | p += p->size; 42 | p->size = units; 43 | } 44 | // p->size == nbytes; remove from list and return 45 | else { 46 | prev_node->next = p->next; 47 | } 48 | 49 | return p + unit_size; 50 | } 51 | 52 | // calloc_j(): same as malloc, but creates an array of count * size length and 53 | // all bytes to zero 54 | void *calloc_j(const size_t size, const size_t count) { 55 | size_t total_bytes = size * count; 56 | void *space = malloc_j(total_bytes); 57 | if (space != NULL) { 58 | memset(space, 0, total_bytes); 59 | } 60 | return space; 61 | } 62 | -------------------------------------------------------------------------------- /ch-8/malloc_j/src/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "malloc_j.h" 5 | 6 | int main() { 7 | if (init(500 * MiB) == -1) { 8 | return -1; 9 | } 10 | printf("bfree() test\n"); 11 | char* baz = malloc((unsigned long)1000); 12 | bfree((void*)baz, 1000); 13 | 14 | char* bar = calloc_j(10, 4); 15 | if (bar == NULL) { 16 | return -1; 17 | } 18 | for (int i = 0; i < 38; i++) { 19 | bar[i] = 'A'; 20 | } 21 | bar[39] = '\0'; 22 | printf("calloc_j test: %s\n", bar); 23 | free_j(bar); 24 | 25 | char* foo = malloc_j(27); 26 | strcpy(foo, "ABCDEFGHIJKLMNOPQRSTUVWXYZ\0"); 27 | if (foo == NULL) { 28 | return -1; 29 | } 30 | printf("malloc_j test: %s\n", foo); 31 | free_j(foo); 32 | 33 | cleanup(); 34 | free(baz); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /ch-8/src/8.1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "common.h" 8 | 9 | /* 10 | * 8.1: Rewrite the program cat from Chapter 7 using read, write, 11 | * open and close instead of their standard library equivalents. 12 | * Perform experiments to determine the relative speeds of the 13 | * two versions. 14 | */ 15 | 16 | int main(int argc, char** argv) { 17 | if (argc < 2) { 18 | printf("usage: cat ...\n"); 19 | } 20 | 21 | int fd = 0; 22 | char temp[MAXLEN] = {0}; 23 | for (int i = 1; i < argc; i++) { 24 | char* filename = argv[i]; 25 | 26 | fd = open(filename, O_RDONLY); 27 | if (fd == -1) { 28 | perror("cat"); 29 | return -1; 30 | } 31 | 32 | while (read(fd, temp, MAXLEN) > 0) { 33 | printf("%s", temp); 34 | } 35 | printf("\n"); 36 | close(fd); 37 | } 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /ch-8/src/8.5.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "8.5.h" 10 | 11 | // main(): recurses through current directory tree calling fsize 12 | int main(int argc, char **argv) { 13 | if (argc == 1) { 14 | fsize("."); 15 | } else { 16 | while (--argc > 0) { 17 | fsize(*(++argv)); 18 | } 19 | } 20 | return 0; 21 | } 22 | 23 | // fsize(): print size and permission bits of all files in dir, recurse into 24 | // subdirs 25 | void fsize(const char *const name) { 26 | struct stat stbuf; 27 | 28 | if (stat(name, &stbuf) == -1) { 29 | fprintf(stderr, "fsize: can't access %s\n", name); 30 | return; 31 | } 32 | if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { 33 | dirwalk(name, fsize); 34 | } 35 | printf("%8ld %4o %s\n", stbuf.st_size, stbuf.st_mode & 0x0fff, name); 36 | } 37 | 38 | // dirwalk: recursively apply fcn to all files in dir 39 | void dirwalk(const char *const dir, void (*fcn)(const char *const)) { 40 | char name[MAX_PATH]; 41 | struct dirent *dp; 42 | DIR *dfd; 43 | 44 | if ((dfd = opendir(dir)) == NULL) { 45 | fprintf(stderr, "dirwalk: can't open %s\n", dir); 46 | return; 47 | } 48 | 49 | while ((dp = readdir(dfd)) != NULL) { 50 | if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { 51 | continue; // skip self and parent 52 | } 53 | if (strlen(dir) + strlen(dp->d_name) + 2 > sizeof(name)) { 54 | fprintf(stderr, "dirwalk: name %s%s too long.\n", dir, dp->d_name); 55 | } else { 56 | sprintf(name, "%s/%s", dir, dp->d_name); 57 | (*fcn)(name); 58 | } 59 | } 60 | closedir(dfd); 61 | } 62 | -------------------------------------------------------------------------------- /common/include/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #define MAXLEN 1000 // max len of any input line 5 | 6 | #ifdef DEBUG 7 | #define LOG(msg) printf("Log: %s\n", msg); 8 | #else 9 | #define LOG(msg) ; 10 | #endif 11 | 12 | // Bitwise functions 13 | int create_mask(const int n, const int position, int* const mask, 14 | const bool inverted); 15 | int clear_bitfield(int* x, const int position, const int n); 16 | 17 | // Character I/O 18 | int getch(void); 19 | void ungetch(int val); 20 | 21 | // Line I/O 22 | int mygetline(char* const line, const int maxline); 23 | int readlines(char** lineptr, const int maxlines); 24 | void writelines(char** lineptr, const int nlines); 25 | void freelines(char** lineptr, const int nlines); 26 | int getword(char* word, int len); 27 | 28 | // Memory 29 | char* myalloc(int n); 30 | void myafree(char* p); 31 | 32 | // Maths 33 | int powi(int base, int exp); 34 | 35 | // Strings 36 | // TODO: Either uncomment this or remove it and make 1.19 static. 37 | void reverse(char* const s, const int len); 38 | -------------------------------------------------------------------------------- /common/include/tests.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void assert_int_eq(const int actual, const int expected, 4 | const char* const func_name, const char* const message); 5 | void assert_int_neq(const int actual, const int expected, 6 | const char* const func_name, const char* const message); 7 | 8 | void assert_double_eq(const double actual, const double expected, 9 | const double error, const char* const func_name, 10 | const char* const message); 11 | void assert_double_neq(const double actual, const double expected, 12 | const double error, const char* const func_name, 13 | const char* const message); 14 | 15 | void assert_string_eq(const char* const actual, const char* const expected, 16 | const char* const func_name, const char* const message); 17 | void assert_string_neq(const char* const actual, const char* const expected, 18 | const char* const func_name, const char* const message); 19 | 20 | void assert_true(const bool expr, const char* const func_name, 21 | const char* const message); 22 | void assert_false(const bool expr, const char* const func_name, 23 | const char* const message); 24 | 25 | void assert_mem_eq(const void* const actual, const void* const expected, 26 | const int size, const char* const func_name, 27 | const char* const message); 28 | void assert_mem_neq(const void* const actual, const void* const expected, 29 | const int size, const char* const func_name, 30 | const char* const message); 31 | -------------------------------------------------------------------------------- /common/include/trees.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef struct tnode { 5 | struct tnode* left; 6 | struct tnode* right; 7 | void* data; 8 | } tnode; 9 | 10 | // basic tree methods 11 | tnode* tree_insert(tnode* const node, const void* const value, 12 | const size_t size, 13 | int (*compare)(const void* const, const void* const)); 14 | tnode* tree_search(const tnode* const node, const void* const value, 15 | int (*compare)(const void* const, const void* const)); 16 | 17 | // memory management 18 | tnode* tnode_alloc(const void* const data); 19 | void tnode_free(tnode* node, void (*cleanup)(void*)); 20 | void tree_cleanup(tnode* node, void (*cleanup)(void*)); 21 | 22 | // traversal 23 | void trav_postorder(const tnode* const node, 24 | void (*display)(const tnode* const)); 25 | void trav_preorder(const tnode* const node, 26 | void (*display)(const tnode* const)); 27 | void trav_inorder(const tnode* const node, void (*display)(const tnode* const)); 28 | -------------------------------------------------------------------------------- /common/src/bitwise.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | 5 | /* 6 | * -------------------------------------------------------- 7 | * Bitwise functions 8 | * -------------------------------------------------------- 9 | */ 10 | 11 | // create_mask(): creates a bitmask where n bits starting at position 12 | // are set (least significant bit is position 0); if inverted is set, 13 | // the bitwise-not of this mask is created instead. 14 | int create_mask(const int n, const int position, int *const mask, 15 | const bool inverted) { 16 | int i, shiftval; 17 | 18 | if (n > 31 || position > 31) { 19 | printf("Error: can't set bits outside of word.\n"); 20 | return -1; 21 | } 22 | 23 | // first set n bits 24 | for (i = 0, *mask = 0; i < n; i++) { 25 | *mask = *mask << 1; 26 | (*mask)++; 27 | } 28 | 29 | // then shift them into position 30 | shiftval = (position == 0) ? 0 : position - 1; // << -x is >> x; avoid this 31 | *mask = *mask << shiftval; 32 | 33 | if (inverted) { 34 | *mask = ~(*mask); 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | // clear_bitfield(): clears n bits starting at position in x 41 | int clear_bitfield(int *const x, const int position, const int n) { 42 | int mask = 0; 43 | 44 | if (n > 31 || position > 31) { 45 | printf("Error: can't clear bits outside of word.\n"); 46 | return -1; 47 | } 48 | 49 | // create inverted mask with n bits starting at p set 50 | if (create_mask(n, position, &mask, true) == -1) { 51 | printf("Error: couldn't create mask.\n"); 52 | return -1; 53 | } 54 | 55 | // bitwise-and mask with x to clear bits in x 56 | *x &= mask; 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /common/src/character.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common.h" 3 | 4 | /* 5 | * -------------------------------------------------------- 6 | * Character I/O 7 | * -------------------------------------------------------- 8 | */ 9 | 10 | #define BUFSIZE 100 11 | static int buf[BUFSIZE]; // buffer for ungetch 12 | static int bufp = 0; 13 | 14 | // getch(): Read characters from input or the character buffer 15 | int getch() { 16 | int gotten; 17 | gotten = (bufp > 0) ? buf[--bufp] : getchar(); 18 | return gotten; 19 | } 20 | 21 | // ungetch(): push characters back to the character buffer 22 | void ungetch(int c) { 23 | if (bufp >= BUFSIZE) { 24 | printf("ungetch: too many characters\n"); 25 | } else { 26 | buf[bufp++] = c; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/maths.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | /* 4 | * -------------------------------------------------------- 5 | * Maths 6 | * -------------------------------------------------------- 7 | */ 8 | 9 | int powi(const int base, const int exp) { 10 | if (exp == 0) { 11 | return 1; 12 | } else { 13 | return base * powi(base, exp - 1); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/src/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common.h" 3 | 4 | /* 5 | * -------------------------------------------------------- 6 | * Memory / storage 7 | * -------------------------------------------------------- 8 | */ 9 | 10 | #define ALLOCSIZE 100 // Amount of available space 11 | 12 | static char allocbuf[ALLOCSIZE]; // storage for alloc 13 | static char *allocp = allocbuf; // next free position 14 | 15 | char *myalloc(int n) // return pointer to n characters 16 | { 17 | // if the requested n bytes is less than or equal the amount of 18 | // remaining free space (base addr + total size - currently used size) 19 | if (allocbuf + ALLOCSIZE - allocp >= n) { 20 | allocp += n; 21 | // printf("alloc(): allocating %d bytes.\n", n); 22 | return allocp - n; // old p 23 | } else { 24 | printf("alloc(): can't allocate %d bytes.\n", n); 25 | return 0; 26 | } 27 | } 28 | 29 | void myafree(char *p) { // free storage pointed to by p 30 | // if the pointer is within the allocbuff: 31 | if (p >= allocbuf && p < allocbuf + ALLOCSIZE) { 32 | allocp = p; // doing this means if you de-allocate a lower address when 33 | // higher ones are allocated, you lose everything above it. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/src/strings.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | /* 4 | * -------------------------------------------------------- 5 | * Strings 6 | * -------------------------------------------------------- 7 | */ 8 | 9 | void reverse(char *const s, const int len) { 10 | int i; 11 | char temp; 12 | for (i = 0; i < len / 2; i++) { 13 | temp = s[len - i - 1]; 14 | s[len - i - 1] = s[i]; 15 | s[i] = temp; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /faq/.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | bin/ 54 | -------------------------------------------------------------------------------- /faq/Makefile: -------------------------------------------------------------------------------- 1 | SHELL:=/bin/bash 2 | CC:= clang 3 | FLAGS := -std=c11 -g -Weverything -Werror 4 | VALGRIND := valgrind -q --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=42 5 | 6 | ### Dockerized Linux workspace for consistent environment 7 | docker-clean: 8 | -docker stop ubuntu 9 | -docker rm ubuntu 10 | 11 | docker: docker-clean 12 | docker pull ubuntu 13 | docker run \ 14 | -dt \ 15 | --name ubuntu \ 16 | -v `pwd`:/c-faq \ 17 | ubuntu 18 | docker exec ubuntu apt-get update 19 | docker exec ubuntu apt-get install -y make valgrind clang clang-tools cdecl perl 20 | 21 | shell: 22 | docker exec -it ubuntu /bin/bash 23 | 24 | workspace: docker-clean docker shell 25 | 26 | clean: 27 | -rm bin/* 28 | 29 | setup: 30 | -mkdir bin 31 | 32 | # Chapter 5 examples 33 | arithmetic-ops-with-pointers \ 34 | arrays-of-arrays \ 35 | assign-to-assignment \ 36 | fucking-for-loops \ 37 | initialization \ 38 | initializing-string-literals \ 39 | long-lines \ 40 | negative-integers \ 41 | null-terminators \ 42 | print-empty-arrays \ 43 | printing-signed \ 44 | returning-structs \ 45 | single-quotes \ 46 | strcmp \ 47 | struct-alignment-bits \ 48 | type-sizes \ 49 | typedef-struct\ 50 | unsigned-overflow: setup 51 | $(CC) $(FLAGS) $@.c -o bin/$@ 52 | ./bin/$@ 53 | -------------------------------------------------------------------------------- /faq/PHILOSOPHY.md: -------------------------------------------------------------------------------- 1 | # Unix Philosophy 2 | * Rule of Modularity: Write simple parts connected by clean interfaces. 3 | * Rule of Clarity: Clarity is better than cleverness. 4 | * Rule of Composition: Design programs to be connected to other programs. 5 | * Rule of Separation: Separate policy from mechanism; separate interfaces from engines. 6 | * Rule of Simplicity: Design for simplicity; add complexity only where you must. 7 | * Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do. 8 | * Rule of Transparency: Design for visibility to make inspection and debugging easier. 9 | * Rule of Robustness: Robustness is the child of transparency and simplicity. 10 | * Rule of Representation: Fold knowledge into data so program logic can be stupid and robust. 11 | * Rule of Least Surprise: In interface design, always do the least surprising thing. 12 | * Rule of Silence: When a program has nothing surprising to say, it should say nothing. 13 | * Rule of Repair: When you must fail, fail noisily and as soon as possible. 14 | * Rule of Economy: Programmer time is expensive; conserve it in preference to machine time. 15 | * Rule of Generation: Avoid hand-hacking; write programs to write programs when you can. 16 | * Rule of Optimization: Prototype before polishing. Get it working before you optimize it. 17 | * Rule of Diversity: Distrust all claims for “one true way”. 18 | * Rule of Extensibility: Design for the future, because it will be here sooner than you think. 19 | 20 | [Source](http://www.catb.org/esr/writings/taoup/html/ch01s06.html) 21 | -------------------------------------------------------------------------------- /faq/README.md: -------------------------------------------------------------------------------- 1 | # C FAQ 2 | Questions I had while learning C and experiments I've done to answer them. All FAQs here are strictly about C11; I make no claims about earlier versions of C. 3 | 4 | ## Use 5 | All programs can be compiled and executed via the Makefile; just run `make ` and the program will compile and automatically execute. 6 | 7 | ## TODO: 8 | - c11 / -Werror -Wall compliant 9 | - keywords descriptions 10 | - examples and experiments from "C Programming FAQs" by Steve Summit. 11 | 12 | -------------------------------------------------------------------------------- /faq/arithmetic-ops-with-pointers.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | /* 5 | K&R say (p. 102, 2nd ed): 6 | "...pointers may be compared under certain circumstances. If p and q point to 7 | members of the same array, then relations like ==, 1=, <, >=, etc., work 8 | properly. For example, p < q is true if p points to an earlier member of the 9 | array than q does. Any pointer can be meaningfully compared for equality or 10 | inequality with zero. But the behavior is undefined for arithmetic or 11 | comparisons with pointers that do not point to members of the same array." 12 | 13 | Let's confirm that! 14 | */ 15 | 16 | long array1[5] = {0, 1, 2, 3, 4}; 17 | long array2[3] = {5, 6, 7}; 18 | 19 | long *ip1, *ip2; 20 | 21 | // < is legal because the array is a contiguous block of bytes 22 | ip1 = &array1[0]; 23 | ip2 = &array1[1]; 24 | printf("ip1, ip2: %p, %p\n", (void *)ip1, (void *)ip2); 25 | printf("*ip1, *ip2: %ld, %ld\n", *ip1, *ip2); 26 | printf("ip1 < ip2: %d\n", ip1 < ip2); 27 | 28 | // < is undefined; we don't know where each thing is on the stack, and as 29 | // such, we can't assume that array1 is above or below array2 address-wise. 30 | ip2 = &array2[1]; 31 | printf("ip1, ip2: %p, %p\n", (void *)ip1, (void *)ip2); 32 | printf("*ip1, *ip2: %ld, %ld\n", *ip1, *ip2); 33 | printf("ip1 < ip2: %d\n", ip1 < ip2); 34 | 35 | // if we subtract p1 from p2 below, do we get the number of elements in the 36 | // array? Yes, make sure to add 1 as the last offset can store a value. This 37 | // works regardless of types. 38 | ip1 = &array1[0]; 39 | ip2 = &array1[4]; 40 | printf("ip2 - ip1: %ld\n", ip2 - ip1 + 1); 41 | 42 | /* 43 | Conclusion: pointer arithmetic is consistent; see K&R p. 103, 2nd ed: 44 | "The valid pointer operations are assignment of pointers of the same type, 45 | adding or subtracting a pointer and an integer, subtracting or comparing two 46 | pointers to members of the same array, and assigning or comparing to zero. All 47 | other pointer arithmetic is illegal. It is not legal to add two pointers, or 48 | to multiply or divide or shift or mask them, or to add float or double to 49 | them, or even, except for void *, to assign a pointer of one type to a pointer 50 | of another type without a cast." 51 | */ 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /faq/arrays-of-arrays.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // K&R exercises 5.8 and 5.9 discuss arrays of arrays, and arrays of pointers, 5 | // and the differences between each. Let's explore that some here. 6 | 7 | int main() { 8 | // daytab is an array of pointers to arrays of chars; these could now 9 | // be of different lengths as opposed to a "true" array of arrays 10 | char *daytab[3]; 11 | 12 | // If we use an array of pointers, each thing pointed-to has to be initialized 13 | // on its own 14 | char non_leap_arr[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 15 | char leap_arr[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 16 | 17 | // Major advantage: arrays of pointers don't have to point to things of the 18 | // same length! 19 | char wtf_leap_arr[] = {0, 127, 127, 127}; 20 | 21 | daytab[0] = non_leap_arr; 22 | daytab[1] = leap_arr; 23 | daytab[2] = wtf_leap_arr; 24 | 25 | printf("daytab: %p\n", (void *)daytab); 26 | printf("daytab[0]: %p\n", (void *)daytab[0]); 27 | printf("non_leap_arr: %p\n", (void *)non_leap_arr); 28 | printf("non_leap_arr[1]: %d\n", non_leap_arr[1]); 29 | printf("&non_leap_arr[1]: %p\n", (void *)&non_leap_arr[1]); 30 | printf("non_leap_arr[2]: %d\n", non_leap_arr[2]); 31 | printf("&non_leap_arr[2]: %p\n\n", (void *)&non_leap_arr[2]); 32 | } 33 | -------------------------------------------------------------------------------- /faq/assign-to-assignment.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // What happens if you assign to an assignment? 4 | 5 | int main() { 6 | // In C, an assignment operation results in the rvalue. 7 | int a, b, c, d; 8 | a = (b = 200); 9 | d = c = 100; 10 | printf("%d %d\n", a, b); 11 | printf("%d %d\n", d, c); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /faq/fucking-for-loops.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // How do I avoid off-by-one errors with for-loops? 4 | 5 | int main() { 6 | int i; 7 | int nums[5] = {0, 1, 2, 3, 4}; 8 | 9 | // nums is {0,1,2,3,4}, so the first char is arr[0] and the last one is 10 | // arr[4], but len(arr) is 5. Generally, your options for going through arrays 11 | // are: 12 | 13 | // Option 1: start at zero, middle condition is "less than length", increment. 14 | for (i = 0; i < 5; i++) { 15 | printf("%d ", nums[i]); 16 | } 17 | printf("\n"); 18 | 19 | // Option 2: start at zero, middle condition is "less or equal to last index", 20 | // increment. 21 | for (i = 0; i <= 4; i++) { 22 | printf("%d ", nums[i]); 23 | } 24 | printf("\n"); 25 | 26 | // Option 3: Go in reverse - start at the last element index, middle condition 27 | // is "greater than or equal to zero", and decrement. 28 | for (i = 4; i >= 0; i--) { 29 | printf("%d ", nums[i]); 30 | } 31 | printf("\n"); 32 | 33 | // Anything else is off-by-one 34 | // Common mistake #1 - middle condition is "less than or equal to length" 35 | // for (i = 0; i <= 5; i++) 36 | for (i = 0; i <= 5; i++) { 37 | printf("%d ", nums[i]); 38 | } 39 | printf("\n"); 40 | 41 | // Common mistake #2 - while reverse-iterating, use "greater than zero" 42 | for (i = 4; i > 0; i--) { 43 | printf("%d ", nums[i]); 44 | } 45 | printf("\n"); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /faq/initialization.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | K&R says (sect. 4.9) that static variables must be initialized with constants, 6 | but that automatic (i.e. variables with function scope) and register variables 7 | can be initialized with other types of expressions. Let's test that! 8 | */ 9 | 10 | int some_func(int z); 11 | 12 | /* x and y are declared, but not initialized */ 13 | static int x; 14 | static int y; 15 | /* 16 | z and w are declared and initialized at the same time. 17 | Note that this won't compile if uncommented - z cannot be assigned to 18 | the return value of a function during compilation (how would 19 | that code run?). 20 | 21 | static int z = some_func(5); 22 | */ 23 | static int w = 10; 24 | 25 | int main() { 26 | /* x is initialized with a constant. */ 27 | x = 5; 28 | /* 29 | y is assigned the return value of a function. Are K&R wrong? 30 | No - this is acceptable at run-time; the only thing not-allowed 31 | for static variables is non-constant initialization at compile time 32 | (i.e. by declaring and initializing simultaneously. 33 | */ 34 | y = some_func(x); 35 | printf("%d\n", y); 36 | 37 | return 0; 38 | } 39 | 40 | int some_func(int z) { return z * x; } 41 | -------------------------------------------------------------------------------- /faq/initializing-string-literals.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // What is the correct way in C to initialize a literal string? 4 | // https://softwareengineering.stackexchange.com/a/183862 5 | 6 | int main() { 7 | /* 8 | Suppose I want to declare the contents of a string at compile time. 9 | I can use a string literal to do this. 10 | */ 11 | char name[14] = {"JOSHUA GOLLER"}; 12 | char name2[] = {"Luke Skywalker"}; 13 | // Note that "JOSHUA GOLLER" has 13 characters, and 14 | // so we initialize an array of 14 chars to include the \0. 15 | // In the second case, the size of the array is taken from the initializer. 16 | 17 | int i = 0; 18 | for (i = 0; i < 13; i++) { 19 | printf("%d, %c\n", name[i], name[i]); 20 | } 21 | printf("\n"); 22 | 23 | // Although the initializer will put a \0 24 | // at the end of name2, this looping method 25 | // is less safe; it's better to be explicit 26 | // about the length of a string than not 27 | // know but assume it's null terminated. 28 | i = 0; 29 | while (name2[i] != '\0') { 30 | printf("%d, %c\n", name2[i], name2[i]); 31 | i++; 32 | } 33 | printf("\n"); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /faq/keywords/README.md: -------------------------------------------------------------------------------- 1 | # c-keywords 2 | The C11 version of my [cpp-keywords project](https://github.com/jsgoller1/cpp-keywords). 3 | 4 | #### Control flow 5 | - [ ] `break` 6 | - [ ] `case` 7 | - [ ] `continue` 8 | - [ ] `default` 9 | - [ ] `do` 10 | - [ ] `for` 11 | - [ ] `goto` 12 | - [ ] `if` 13 | - [ ] `return` 14 | - [ ] `switch` 15 | - [ ] `while` 16 | 17 | #### Basic types 18 | - [ ] `char` 19 | - [ ] `double` 20 | - [ ] `float` 21 | - [ ] `int` 22 | - [ ] `long` 23 | - [ ] `short` 24 | - [ ] `signed / unsigned` 25 | - [ ] `sizeof` (not a type) 26 | - [ ] `void` 27 | 28 | #### Advanced types 29 | - [ ] `enum` 30 | - [ ] `struct` 31 | - [ ] `union` 32 | - [ ] `typedef` 33 | 34 | #### Type/storage modifiers 35 | - [ ] `auto` 36 | - [ ] `const` 37 | - [ ] `else` 38 | - [ ] `extern` 39 | - [ ] `inline (since C99)` 40 | - [ ] `register` 41 | - [ ] `static` 42 | - [ ] `volatile` 43 | 44 | #### Preprocessor macros (prefixed with '#') 45 | - [ ] `if` 46 | - [ ] `elif` 47 | - [ ] `else` 48 | - [ ] `endif` 49 | - [ ] `defined` 50 | - [ ] `ifdef` 51 | - [ ] `ifndef` 52 | - [ ] `define` 53 | - [ ] `undef` 54 | - [ ] `include` 55 | - [ ] `line` 56 | - [ ] `error` 57 | - [ ] `pragma` 58 | 59 | #### Other / unsorted 60 | - [ ] `restrict (since C99)` 61 | 62 | ### Underscore keywords and macros 63 | - [ ] `_Alignas` (since C11; `alignas` in `stdalign.h`) 64 | r [ ] `_Alignof` (since C11; `alignof` macro in `stdalign.h`) 65 | - [ ] `_Atomic` (since C11; `atomic_bool`, `atomic_int`, etc. macros in `stdatomic.h`) 66 | - [ ] `_Bool` (since C99; `bool` macro in `stdbool.h`) 67 | - [ ] `_Complex` (since C99; `complex` macro in `complex.h`) 68 | - [ ] `_Generic` (since C11, no macro) 69 | - [ ] `_Imaginary` (since C99; `imaginary` macro in `complex.h`) 70 | - [ ] `_Noreturn` (since C11; `noreturn` macro in `stdnoreturn.h`) 71 | - [ ] `_Static_assert` (since C11; `static_assert` macro in `assert.h`) 72 | - [ ] `_Thread_local` (since C11; `thread_local` macro in `threads.h`) 73 | -------------------------------------------------------------------------------- /faq/long-lines.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // In K&R ex. 5-13, I am not sure how to read insanely long lines from stdin in 5 | // a memory efficient way without storing them in a huge but temporary buffer 6 | // first. I want to see if GNU tail runs into similar issues if I feed it huge 7 | // lines. 8 | 9 | // Answer - yes, somehow it manages to handle arbitrarily long lines. 10 | 11 | int main() { 12 | unsigned long long line_len; 13 | line_len = 0xFFFFFFFF; 14 | 15 | printf("%llu: ", line_len); 16 | 17 | for (unsigned long long i = 0; i < line_len; i++) { 18 | printf("X"); 19 | } 20 | printf("\n"); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /faq/null-terminators.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Why is it necessary to have a null terminator at the end of a char[] buffer? 4 | 5 | int main() { 6 | // chars_a and chars_b contain the same thing, as 7 | // C buffers are zero-initialized. 8 | int i, len; 9 | len = 4; 10 | char chars_a[4] = {'\0', '\0', '\0', '\0'}; 11 | char chars_b[len]; 12 | 13 | // This is BAD; chars_b will be: {'A', 'A', 'A', 'A'} 14 | for (i = 0; i < len; i++) { 15 | chars_b[i] = 'A'; 16 | } 17 | 18 | // There's now no null terminator! 19 | for (i = 0; i < len; i++) { 20 | if (chars_b[i] == '\0') { 21 | printf("Null terminator.\n"); 22 | } else { 23 | printf("Not null: %c\n", chars_b[i]); 24 | } 25 | } 26 | // Why is this bad? printf will go on to print whatever 27 | // is outside the buffer. Notice we get more than chars_b 28 | // when we go to print it: 29 | for (i = 4; i < 11; i++) { 30 | chars_b[i] = '!'; 31 | } 32 | printf("%s\n", chars_b); 33 | // But if we stick a null at the end, we will stop printing at the end of the 34 | // buffer: 35 | chars_b[3] = '\0'; 36 | printf("%s\n", chars_b); 37 | 38 | // Another example - let's say we have two strings contiguous in memory, 39 | // separated by null a terminator. 40 | char chars_c[8] = {'o', 'n', 'e', '\0', 't', 'w', 'o', '\0'}; 41 | printf("%s\n", chars_c); 42 | // If we replace the null terminator with something else, we get them both. 43 | chars_c[3] = '!'; 44 | printf("%s\n", chars_c); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /faq/print-empty-arrays.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // What does an "empty" value look like in an array? 4 | 5 | int main() { 6 | char chars[4] = {'a', 'b', 'c', 'd'}; 7 | chars[1] = ""; 8 | // prints "acd" 9 | printf("%s\n", chars); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /faq/printing-signed.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Does the binary representation of 0x80000000 + 0x00000001 change 4 | // depending on the type each is declared as? 5 | 6 | int main() { 7 | unsigned int a = 0x80000000; 8 | unsigned int b = 0x00000001; 9 | 10 | signed int c = 0x80000000; 11 | signed int d = 0x00000001; 12 | 13 | // 0x80000001 = unsigned 2147483649, signed -2147483647 14 | printf("0x%x = unsigned %u, signed %d\n", a + b, a + b, a + b); 15 | 16 | // 0x80000001 = signed -2147483647, unsigned 2147483649 17 | printf("0x%x = signed %d, unsigned %u\n", c + d, c + d, c + d); 18 | 19 | // Note that in C, 0x80000000 + 0x00000001 = 0x80000001 always, 20 | // but the value associated with 0x80000001 depends on the type. 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /faq/returning-structs.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | K&R chapter 6.2 shows the function makepoint() as an example 5 | on initializing structs. This felt like it had to be wrong since 6 | temp would get cleaned up after the function returns. Does it? 7 | When is it invalid to use stack memory from a function? 8 | 9 | The answer here is that valued _passed as function arguments_ are 10 | copied by the callee - unless you pass a pointer (in which case 11 | the address is copied to the callee, which can be dereferenced to the 12 | original value), modifying arguments of the function inside the function 13 | body has no effect outside the function. On the other hand, returned 14 | values are copied back to the caller. 15 | 16 | Consider this - malloc()'s signature is void *malloc(size_t size). It 17 | takes an int argument and returns a pointer. This means that somewhere 18 | in the body, a void* is declared and then returned to the callee - so 19 | the above statement about return values vs passed values must hold. 20 | */ 21 | 22 | struct point makepoint(int x, int y); 23 | void bad_makepoint(struct point mystruct); 24 | 25 | struct point { 26 | int x; 27 | int y; 28 | }; 29 | 30 | // makepoint: make a point from x and y components 31 | struct point makepoint(int x, int y) { 32 | struct point temp; 33 | temp.x = x; 34 | temp.y = y; 35 | return temp; 36 | } 37 | 38 | void bad_makepoint(struct point mystruct) { 39 | mystruct.x = 10; 40 | mystruct.y = 20; 41 | } 42 | 43 | int main() { 44 | // This is allowed; the struct is returned, so the values are copied from the 45 | // callee to the caller. 46 | struct point joshua; 47 | joshua = makepoint(15, 16); 48 | printf("joshua.x: %d, joshua.y: %d\n", joshua.x, joshua.y); 49 | 50 | // This is treif; the struct is copied to the callee and cleaned up after 51 | struct point nope; 52 | nope.x = 666; 53 | nope.y = 666; 54 | bad_makepoint(nope); 55 | printf( 56 | "The struct is unchanged by bad_makepoint() - nope.x: %d, nope.y: %d\n", 57 | nope.x, nope.y); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /faq/single-quotes.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // What is the difference between using double and single quotes? 4 | 5 | int main() { 6 | // Int value of special character, here 10 7 | int a = '\n'; 8 | printf("%d\n", a); 9 | 10 | // Special character 11 | char b = '\n'; 12 | printf("%c\n", b); 13 | 14 | // Int value of the character, here 65 15 | int c = 'A'; 16 | printf("%d\n", c); 17 | 18 | // Actual character 19 | char d = 'A'; 20 | printf("%c\n", d); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /faq/strcmp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * The documentation for strcmp(char* a, char* b) says it returns a value based 6 | * on whether a is "lexicographically greater" than b; what exactly does this 7 | * mean? 8 | */ 9 | 10 | static void compare(const char* const a, const char* const b) { 11 | int result = strcmp(a, b); 12 | printf("comparing: %s and %s: %d\n", a, b, result); 13 | } 14 | 15 | int main() { 16 | // How is the return value calculated? 17 | // Answer: The first character in which the strings differ 18 | // is found, the second string's character is subtracted from the first's. 19 | // So, a comes before b in the alphabet, meaning that the result of 20 | // strcmp(a,b) will be negative, whereas strcmp(b, a) will be positive. 21 | compare("CATx", "CATz"); 22 | printf("%c (%d) - %c (%d): %d\n", 'x', 'x', 'z', 'z', 'x' - 'z'); 23 | 24 | // If just the first letter in which the strings differ is used, 25 | // do the preceeding characters make a difference? 26 | // Answer: No, the result is the same. 27 | compare("AAAAAAAz", "AAAAAAAx"); 28 | compare("ZZZZZZZz", "ZZZZZZZx"); 29 | 30 | // Does the length of the string matter? 31 | // Answer: No, just the last character. 32 | compare("a", "z"); 33 | compare("aa", "zz"); 34 | 35 | // Does the order of the arguments matter? 36 | // Answer: Yes - but you get the negative version of the same answer. 37 | compare("aa", "zz"); 38 | compare("zz", "aa"); 39 | 40 | // What about for case-different versions of the same string? 41 | // Answer: Same answer, as long as the case is consistent 42 | compare("AA", "ZZ"); 43 | compare("aa", "zz"); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /faq/struct-alignment-bits.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Historically, I've included a 'char _align[n]' element in structs that didn't 4 | // perfectly align to word boundaries; clang would throw a -Wpadded error if 5 | // not. I think the right way to handle this is to just disable the warning / 6 | // let the compiler handle the padding, but can you use bit fields to do 7 | // padding? 8 | 9 | struct weird { 10 | int x; // 4 11 | char y; // 1 12 | int z : 24; 13 | }; 14 | 15 | int main() { 16 | struct weird w = {10, 'a'}; 17 | printf("%d\n", w.x); 18 | } 19 | -------------------------------------------------------------------------------- /faq/type-sizes.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | In case I forget, here's a quick demo of the sizes 5 | of each type on my machine (an Intel 6800k running x64 Ubuntu 17.10) 6 | */ 7 | 8 | int main() { 9 | printf("char: %lu bytes, %lu bits\n", sizeof(char), sizeof(char) * 8); 10 | printf("short: %lu bytes, %lu bits\n", sizeof(short), sizeof(short) * 8); 11 | printf("int: %lu bytes, %lu bits\n", sizeof(int), sizeof(int) * 8); 12 | printf("long: %lu bytes, %lu bits\n", sizeof(long), sizeof(long) * 8); 13 | printf("long long: %lu bytes, %lu bits\n", sizeof(long long), 14 | sizeof(long long) * 8); 15 | printf("float: %lu bytes, %lu bits\n", sizeof(float), sizeof(float) * 8); 16 | printf("double: %lu bytes, %lu bits\n\n", sizeof(double), sizeof(double) * 8); 17 | 18 | // What about pointers? They're addresses, their size is the wordsize for your 19 | // CPU. 20 | char* x; 21 | printf("long pointer: %lu bytes, %lu bits\n", sizeof(x), sizeof(x) * 8); 22 | long* y; 23 | printf("long pointer: %lu bytes, %lu bits\n\n", sizeof(y), sizeof(y) * 8); 24 | 25 | // What about structs? Depends what you put in them! 26 | struct smalls { 27 | char a; 28 | char b; 29 | }; 30 | 31 | struct bigs { 32 | long a; 33 | long b; 34 | }; 35 | 36 | // Structs of mixed types can have weird results depending on 37 | // how the compiler handles padding; this will sometimes throw 38 | // warnings (which are currently disabled via -Wno-padded in the 39 | // Makefile) 40 | struct mixed1 { 41 | char a; 42 | char b; 43 | char c; 44 | }; 45 | 46 | struct mixed2 { 47 | float c; 48 | char a; 49 | char b; 50 | }; 51 | printf("smalls: %lu bytes, %lu bits\n", sizeof(struct smalls), 52 | sizeof(struct smalls) * 8); 53 | printf("bigs: %lu bytes, %lu bits\n", sizeof(struct bigs), 54 | sizeof(struct bigs) * 8); 55 | printf("mixed1: %lu bytes, %lu bits\n", sizeof(struct mixed1), 56 | sizeof(struct mixed1) * 8); 57 | printf("mixed2: %lu bytes, %lu bits\n\n", sizeof(struct mixed2), 58 | sizeof(struct mixed2) * 8); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /faq/typedef-struct.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // I've always been confused about struct syntax 4 | // with typedef. Here's some examples. 5 | 6 | // This declares a kind of struct called "point_2d" 7 | struct point_2d { 8 | int x; 9 | int y; 10 | }; 11 | 12 | int main() { 13 | // We can use point_2d like this, and reference it 14 | // via "p": 15 | struct point_2d p; 16 | p.x = 5; 17 | printf("p.x: %d\n", p.x); 18 | 19 | /* 20 | Now for some confusing syntax. If we're _not_ using 21 | typedef, then a lexeme present in between the closing } 22 | and the semicolon in the struct declaration will cause 23 | the statement to be parsed as an inline struct definition 24 | and declaration(s) with the lexemes being names of 25 | instances of that struct. 26 | */ 27 | struct point_3d { 28 | int x; 29 | int y; 30 | int z; 31 | } inline_struct_1, inline_struct_2; 32 | 33 | // inline_struct_1 and inline_struct_2 are now structs of type point_3d 34 | inline_struct_1.x = 666; 35 | printf("inline_struct_1.x: %d\n", inline_struct_1.x); 36 | inline_struct_2.x = 777; 37 | printf("inline_struct_2.x: %d\n", inline_struct_2.x); 38 | 39 | // We can reuse the above struct definition, despite 40 | // it being inline. 41 | struct point_3d not_inline; 42 | not_inline.x = 888; 43 | printf("not_inline.x: %d\n", not_inline.x); 44 | 45 | /* 46 | On the other hand, if we use the typedef keyword, 47 | this changes. Recall that the syntax is: typedef 48 | Here's one way to create a struct and then typedef it 49 | to something simpler: 50 | */ 51 | struct point_4d { 52 | int x; 53 | int y; 54 | int z; 55 | int w; 56 | }; 57 | typedef struct point_4d Point; 58 | 59 | /* 60 | However, this can be expressed more concisely. 61 | The following syntax creates an inline struct 62 | definition called point_4d_v2 and typedefs it 63 | to Point_v2: 64 | */ 65 | typedef struct point_4d_v2 { 66 | int x; 67 | int y; 68 | int z; 69 | int w; 70 | } Point_v2; 71 | 72 | // Then we can use Point/Point_v2 the way you'd expect: 73 | Point k; 74 | Point_v2 k2; 75 | k.w = 15; 76 | k2.w = 16; 77 | printf("k.w: %d\n", k.w); 78 | printf("k2.w: %d\n", k2.w); 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /faq/unsigned-overflow.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Appendix A of K&R says: 5 | * Unsigned integers, declared using the keyword unsigned, obey the laws of 6 | * arithmetic modulo 2^n where n is the number of bits in the representation, 7 | * and thus arithmetic on unsigned quantities can never overflow. 8 | * 9 | * So what happens if we try to overflow it anyways? 10 | */ 11 | 12 | int main() { 13 | // Prints 4294967294 or 0xfffffffe. Wat? 14 | unsigned int max = 0xFFFFFFFF; 15 | printf("%u + %u = %u\n", max, max, max + max); 16 | printf("%x + %x = %x\n", max, max, max + max); 17 | 18 | // What happens if we do this with long types? 19 | // We see that the true result is 0x1fffffffe; 20 | // the lower 32 bits are set, but since the int couldn't 21 | // hold the 33rd bit, it was chopped off - no overflow occurred. 22 | unsigned long lmax = 0x00000000FFFFFFFF; 23 | printf("%lu + %lu = %lu\n", lmax, lmax, lmax + lmax); 24 | printf("%lx + %lx = %lx\n", lmax, lmax, lmax + lmax); 25 | } 26 | -------------------------------------------------------------------------------- /makefiles/building.mk: -------------------------------------------------------------------------------- 1 | ### Uncomment this to run Clang's static analyzer while building; this makes the build slower. 2 | ANALYZER:=scan-build --status-bugs 3 | 4 | ### Compiler settings 5 | CC:=clang 6 | CFLAGS :=-std=gnu11 -g -lm 7 | WARNINGS :=-Weverything -Werror 8 | INCLUDES :=-I common/include 9 | LIBS := common/src/*.c 10 | EXTRA_FLAGS:=-D TEST_OUTPUT 11 | COMPILE:=$(ANALYZER) $(CC) $(CFLAGS) $(WARNINGS) $(EXTRA_FLAGS) $(INCLUDES) $(LIBS) 12 | 13 | ### Uncomment this if you want to run the tests; this makes the build slower. 14 | RUN_TESTS:=true 15 | 16 | ### Uncomment this to do memory leak analysis while running; this makes tests run (slightly) slower 17 | VALGRIND := valgrind -q --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=42 18 | 19 | # Each individual make target runs a build target 20 | # matching to this to produce the compiled binary. 21 | %-build: clean 22 | $(eval CHAPTER := ch-$(shell echo $@ | sed -E 's/\.([0-9]*|[0-9]*-[0-9]*)-build//g')) 23 | $(eval EXERCISE := $(shell echo $@ | sed 's/-build//')) 24 | $(COMPILE) -I $(CHAPTER)/include $(CHAPTER)/src/$(EXERCISE).c -o bin/$(EXERCISE) 25 | 26 | ### Exercises that do not need any input for testing can use this target for testing; 27 | ### otherwise, they will run tests their own way. 28 | %-basic-test: 29 | ifdef RUN_TESTS 30 | $(eval EXERCISE := $(shell echo $@ | sed 's/-basic-test//')) 31 | $(VALGRIND) ./bin/$(EXERCISE) 32 | endif 33 | 34 | ### Binary cleanup 35 | setup: 36 | -mkdir bin 37 | 38 | clean: 39 | -rm -r bin/* 40 | -------------------------------------------------------------------------------- /makefiles/docker.mk: -------------------------------------------------------------------------------- 1 | ### Dockerized Linux workspace for consistent environment 2 | docker-clean: 3 | -docker stop ubuntu 4 | -docker rm ubuntu 5 | 6 | docker: docker-clean 7 | docker pull ubuntu 8 | docker run \ 9 | -dt \ 10 | --name ubuntu \ 11 | -v `pwd`:/k-and-r \ 12 | ubuntu 13 | docker exec ubuntu apt-get update 14 | docker exec ubuntu apt-get install -y make valgrind clang clang-tools cdecl perl 15 | 16 | shell: 17 | docker exec -it ubuntu /bin/bash 18 | 19 | workspace: docker-clean docker shell 20 | --------------------------------------------------------------------------------