In this tutorial, we define array interfaces and try to implement them in C.
Array is a container holding multiple values of the same type, and can be read, written with random offset.
Generally, array should support constant time complexity in random access(read and write). An array that supports resize is a dynamic array, otherwise, it is a static size array.
In C++, array is implemented as std::vector. In Python, array is implemented as list. In C, you have to do it yourself.
Given any array type Array, it should support these methods:
void ArrayInit(Array* array, size_t size)
Initial the array conaining size elements.
A impelementation might requires more information in the ArrayInit, such as the type information to manage the resource holded by elements.
void ArrayRelease(Array* array)
Release the array. After this, the array content is invalid. But a implementation might leave the array as initialized with zero.
size_t ArraySize(const Array* array)
Returns the number of elements stored in array.
void ArrayResize(const Array* array, size_t new_size)
Truncates or extends array. It is a fatal error if this failed. Implementation can change the interface to return boolean result.
void* ArrayGet(Array* array, size_t i)
Returns the adress of element at offset i.
An offset should be less than the array size. It is a runtime error if an offset is larger than ArraySize(array). Checking this or not, is a implementation behavior. It is usually good to add this check in debug mode for test, and disable the check in release mode for performance, see assert function.
A non general array type can have type information in these methods. For example, an array of double can be defined by these accessing methods:
void DoubleArrayInit(DoubleArray* array, size_t size)void DoubleArrayRelease(DoubleArray* array)size_t DoubleArraySize(const DoubleArray* array)void DoubleArrayResize(DoubleArray* array, size_t new_size)double DoubleArrayGet(DoubleArray* array, size_t i)void DoubleArraySet(DoubleArray* array, size_t i, double v)Compare this list with the general array, we have simpler and type safe read and write methods. Since it can only be used for double, the implementation can be much simple, and has best performance. In C, a common approach to achieve type safe and best runtime performance, without code duplication for every type, is using preprocessor macro.
Based on these primitive methods, it is quite easy and straight to support more methods, like append an element, insert an element, erase an element, append an array, insert an array, erase an slice.
Let us start from the simpler version, a static plain type array.
Following C++, We call a type is a plain type, if we do not need to construct and destruct it, the data can be copied from one to another value by memory.
In C, we need to manage all memory manually. In the implementation of DoubleArray, we need to call DoubleArrayInit to initialize an array, and DoubleArrayRelease to release memory allocated intenrally by the array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
To implement a general type Array, we need to abstract some common routines to manage data of a dynamic type. We need pass these routines in the ArrayInit, and in ArrayRelease, we also need to release resources allocated by array elements. The initialization and release might also happen in array management, like resize, append, erase.
We abstract a general type management routines as struct TypeInfo, and believe this can be allocated statistically(or in stack), so a const pointer of TypeInfo can be passed to ArrayInit without life time problem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | |
Before reading this file, you are encouraged to implement yourself version, to make array_test pass, see Problems sections.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | |
Assume we copy the relative code to file double_array.h, array.h, array.c and array_test.c respectively, this code can be compiled and tested as:
# clang can be replacce by gcc, or cc
clang -std=c11 -Wall -O0 -g array_test.cc array.c -o array_test
./arary_test
# If valgrind is installed.
valgrind ./array_test
If you implement yourself version array.c in another file array_ya.c, then it can be tested as:
clang -std=c11 -Wall -O0 -g array_test.cc array_ya.c -o array_ya_test
./arary_ya_test
# If valgrind is installed.
valgrind ./array_ya_test
Problem This code is not tested, please write some code to test this implementation.
Problem Implement double Sum(DoubleArray*array), returns the summation of all values in the array.
Problem Implement size_t ArgMax(DoubleArray*array), which returns the index of max value in the array, and returns zero for empty array.
Problem Implement void DoubleArrayAppend(DoubleArray* array, double v) based on the other access methods in above, without touching DoubleArray fields.
Problem Since the DoubleArray's implementation is quite simple, we can copy code and rename the type for IntArray, Int64Array. Try to define a macro PLAIN_ARRAY(name, type), which generate array code automatically. For example, PLAIN_ARRAY(DoubleArray, double) can generate the code for DoubleArray, PLAIN_ARRAY(IVec, int) generate code for int array, with all methods prefixed by IVec.
Problem Re-implement all Array methods.
Problem Implement a function to swap two non interleaved ranges of array:
void ArraySwapRanges(Array* array, size_t first_offset, size_t first_len, size_t second_offset, size_t second_len)
For example, for arary [1, 2, 3, 4, 5, 6, 7, 8], calling ArraySwapRanges(array, 1, 2, 4, 3) will modifiy the array into [1, 5, 6, 7, 4, 2, 3, 8].