1 | \chapter{Background} |
---|
2 | |
---|
3 | Since this work builds on C, it is necessary to explain the C mechanisms and their shortcomings for array, linked list, and string. |
---|
4 | |
---|
5 | |
---|
6 | \section{Ill-typed expressions} |
---|
7 | |
---|
8 | C reports many ill-typed expressions as warnings. |
---|
9 | For example, these attempts to assign @y@ to @x@ and vice-versa are obviously ill-typed. |
---|
10 | \lstinput{12-15}{bkgd-c-tyerr.c} |
---|
11 | with warnings: |
---|
12 | \begin{cfa} |
---|
13 | warning: assignment to 'float *' from incompatible pointer type 'void (*)(void)' |
---|
14 | warning: assignment to 'void (*)(void)' from incompatible pointer type 'float *' |
---|
15 | \end{cfa} |
---|
16 | Similarly, |
---|
17 | \lstinput{17-19}{bkgd-c-tyerr.c} |
---|
18 | with warning: |
---|
19 | \begin{cfa} |
---|
20 | warning: passing argument 1 of 'f' from incompatible pointer type |
---|
21 | note: expected 'void (*)(void)' but argument is of type 'float *' |
---|
22 | \end{cfa} |
---|
23 | with a segmentation fault at runtime. |
---|
24 | Clearly, @gcc@ understands these ill-typed case, and yet allows the program to compile, which seems inappropriate. |
---|
25 | Compiling with flag @-Werror@, which turns warnings into errors, is often too pervasive, because some warnings are just warnings, \eg an unused variable. |
---|
26 | In the following discussion, ``ill-typed'' means giving a nonzero @gcc@ exit condition with a message that discusses typing. |
---|
27 | Note, \CFA's type-system rejects all these ill-typed cases as type mismatch errors. |
---|
28 | |
---|
29 | % That @f@'s attempt to call @g@ fails is not due to 3.14 being a particularly unlucky choice of value to put in the variable @pi@. |
---|
30 | % Rather, it is because obtaining a program that includes this essential fragment, yet exhibits a behaviour other than "doomed to crash," is a matter for an obfuscated coding competition. |
---|
31 | |
---|
32 | % A "tractable syntactic method for proving the absence of certain program behaviours by classifying phrases according to the kinds of values they compute"*1 rejected the program. |
---|
33 | % The behaviour (whose absence is unprovable) is neither minor nor unlikely. |
---|
34 | % The rejection shows that the program is ill-typed. |
---|
35 | % |
---|
36 | % Yet, the rejection presents as a GCC warning. |
---|
37 | % *1 TAPL-pg1 definition of a type system |
---|
38 | |
---|
39 | |
---|
40 | \section{Reading declarations} |
---|
41 | |
---|
42 | A significant area of confusion for reading C declarations results from: |
---|
43 | \begin{itemize} |
---|
44 | \item |
---|
45 | C is unique in having dimension be higher priority than pointer in declarations.\footnote{ |
---|
46 | For consistency, subscript has higher priority than dereference, yielding \lstinline{(*arp)[3]} rather than \lstinline{*arp[3]}.} |
---|
47 | \item |
---|
48 | Embedding a declared variable in a declaration, mimics the way the variable is used in executable statements. |
---|
49 | \end{itemize} |
---|
50 | \begin{cquote} |
---|
51 | \begin{tabular}{@{}ll@{}} |
---|
52 | \multicolumn{1}{@{}c}{\textbf{Array}} & \multicolumn{1}{c@{}}{\textbf{Function Pointer}} \\ |
---|
53 | \begin{cfa} |
---|
54 | int @(*@ar@)[@5@]@; // definition |
---|
55 | ... @(*@ar@)[@3@]@ += 1; // usage |
---|
56 | \end{cfa} |
---|
57 | & |
---|
58 | \begin{cfa} |
---|
59 | int @(*@f@())[@5@]@ { ... }; // definition |
---|
60 | ... @(*@f@())[@3@]@ += 1; // usage |
---|
61 | \end{cfa} |
---|
62 | \end{tabular} |
---|
63 | \end{cquote} |
---|
64 | The parenthesis are necessary to achieve a pointer to a @T@, and the type is wrapped around the name in successive layers (like an \Index{onion}) to match usage in an expression. |
---|
65 | While attempting to make the two contexts consistent is a laudable goal, it has not worked out in practice, even though Dennis Richie believed otherwise: |
---|
66 | \begin{quote} |
---|
67 | In spite of its difficulties, I believe that the C's approach to declarations remains plausible, and am comfortable with it; it is a useful unifying principle.~\cite[p.~12]{Ritchie93} |
---|
68 | \end{quote} |
---|
69 | After all, reading a C array type is easy: just read it from the inside out, and know when to look left and when to look right! |
---|
70 | |
---|
71 | \CFA provides its own type, variable and routine declarations, using a simpler syntax. |
---|
72 | The new declarations place qualifiers to the left of the base type, while C declarations place qualifiers to the right of the base type. |
---|
73 | The qualifiers have the same syntax and semantics in \CFA as in C. |
---|
74 | Then, a \CFA declaration is read left to right, where a function return type is enclosed in brackets @[@\,@]@. |
---|
75 | \begin{cquote} |
---|
76 | \begin{tabular}{@{}l@{\hspace{3em}}ll@{}} |
---|
77 | \multicolumn{1}{c@{\hspace{3em}}}{\textbf{C}} & \multicolumn{1}{c}{\textbf{\CFA}} & \multicolumn{1}{c}{\textbf{read left to right}} \\ |
---|
78 | \begin{cfa} |
---|
79 | int @*@ x1 @[5]@; |
---|
80 | int @(*@x2@)[5]@; |
---|
81 | int @(*@f( int p )@)[5]@; |
---|
82 | \end{cfa} |
---|
83 | & |
---|
84 | \begin{cfa} |
---|
85 | @[5] *@ int x1; |
---|
86 | @* [5]@ int x2; |
---|
87 | @[ * [5] int ]@ f( int p ); |
---|
88 | \end{cfa} |
---|
89 | & |
---|
90 | \begin{cfa} |
---|
91 | // array of 5 pointers to int |
---|
92 | // pointer to array of 5 int |
---|
93 | // function returning pointer to array of 5 ints |
---|
94 | \end{cfa} |
---|
95 | \\ |
---|
96 | & & |
---|
97 | \LstCommentStyle{//\ \ \ and taking an int argument} |
---|
98 | \end{tabular} |
---|
99 | \end{cquote} |
---|
100 | As declaration size increases, it becomes corresponding difficult to read and understand the C declaration form, whereas reading and understanding a \CFA declaration has linear complexity as the declaration size increases. |
---|
101 | Note, writing declarations left to right is common in other programming languages, where the function return-type is often placed after the parameter declarations, \eg \CC \lstinline[language=C++]{auto f( int ) -> int}. |
---|
102 | Unfortunately, \CFA cannot interchange the priorities of subscript and dereference in expressions without breaking C compatibility. |
---|
103 | |
---|
104 | \VRef[Table]{bkgd:ar:usr:avp} introduces the many layers of the C and \CFA array story, where the \CFA story is discussion in \VRef[Chapter]{c:Array}. |
---|
105 | The \CFA-thesis column shows the new array declaration form, which is my contribution to safety and ergonomics. |
---|
106 | The table shows there are multiple yet equivalent forms for the array types under discussion, and subsequent discussion shows interactions with orthogonal (but easily confused) language features. |
---|
107 | Each row of the table shows alternate syntactic forms. |
---|
108 | The simplest occurrences of types distinguished in the preceding discussion are marked with $\triangleright$. |
---|
109 | Removing the declared variable @x@, gives the type used for variable, structure field, cast, or error messages. |
---|
110 | Unfortunately, parameter declarations have more syntactic forms and rules. |
---|
111 | |
---|
112 | \begin{table} |
---|
113 | \centering |
---|
114 | \caption{Syntactic Reference for Array vs Pointer. Includes interaction with \lstinline{const}ness.} |
---|
115 | \label{bkgd:ar:usr:avp} |
---|
116 | \begin{tabular}{ll|l|l|l} |
---|
117 | & Description & \multicolumn{1}{c|}{C} & \multicolumn{1}{c|}{\CFA} & \multicolumn{1}{c}{\CFA-thesis} \\ |
---|
118 | \hline |
---|
119 | $\triangleright$ & value & @T x;@ & @T x;@ & \\ |
---|
120 | \hline |
---|
121 | & immutable value & @const T x;@ & @const T x;@ & \\ |
---|
122 | & & @T const x;@ & @T const x;@ & \\ |
---|
123 | \hline \hline |
---|
124 | $\triangleright$ & pointer to value & @T * x;@ & @* T x;@ & \\ |
---|
125 | \hline |
---|
126 | & immutable ptr. to val. & @T * const x;@ & @const * T x;@ & \\ |
---|
127 | \hline |
---|
128 | & ptr. to immutable val. & @const T * x;@ & @* const T x;@ & \\ |
---|
129 | & & @T const * x;@ & @* T const x;@ & \\ |
---|
130 | \hline \hline |
---|
131 | $\triangleright$ & array of value & @T x[10];@ & @[10] T x@ & @array(T, 10) x@ \\ |
---|
132 | \hline |
---|
133 | & ar.\ of immutable val. & @const T x[10];@ & @[10] const T x@ & @const array(T, 10) x@ \\ |
---|
134 | & & @T const x[10];@ & @[10] T const x@ & @array(T, 10) const x@ \\ |
---|
135 | \hline |
---|
136 | & ar.\ of ptr.\ to value & @T * x[10];@ & @[10] * T x@ & @array(T *, 10) x@ \\ |
---|
137 | & & & & @array(* T, 10) x@ \\ |
---|
138 | \hline |
---|
139 | & ar.\ of imm. ptr.\ to val. & @T * const x[10];@ & @[10] const * T x@ & @array(* const T, 10) x@ \\ |
---|
140 | & & & & @array(const * T, 10) x@ \\ |
---|
141 | \hline |
---|
142 | & ar.\ of ptr.\ to imm. val. & @const T * x[10];@ & @[10] * const T x@ & @array(const T *, 10) x@ \\ |
---|
143 | & & @T const * x[10];@ & @[10] * T const x@ & @array(* const T, 10) x@ \\ |
---|
144 | \hline \hline |
---|
145 | $\triangleright$ & ptr.\ to ar.\ of value & @T (*x)[10];@ & @* [10] T x@ & @* array(T, 10) x@ \\ |
---|
146 | \hline |
---|
147 | & imm. ptr.\ to ar.\ of val. & @T (* const x)[10];@ & @const * [10] T x@ & @const * array(T, 10) x@ \\ |
---|
148 | \hline |
---|
149 | & ptr.\ to ar.\ of imm. val. & @const T (*x)[10];@ & @* [10] const T x@ & @* const array(T, 10) x@ \\ |
---|
150 | & & @T const (*x)[10];@ & @* [10] T const x@ & @* array(T, 10) const x@ \\ |
---|
151 | \hline |
---|
152 | & ptr.\ to ar.\ of ptr.\ to val. & @T *(*x)[10];@ & @* [10] * T x@ & @* array(T *, 10) x@ \\ |
---|
153 | & & & & @* array(* T, 10) x@ \\ |
---|
154 | \hline |
---|
155 | \end{tabular} |
---|
156 | \end{table} |
---|
157 | |
---|
158 | |
---|
159 | \section{Array} |
---|
160 | \label{s:Array} |
---|
161 | |
---|
162 | At the start, the C language designers made a significant design mistake with respect to arrays. |
---|
163 | \begin{quote} |
---|
164 | In C, there is a strong relationship between pointers and arrays, strong enough that pointers and arrays really should be treated simultaneously. |
---|
165 | Any operation which can be achieved by array subscripting can also be done with pointers.~\cite[p.~93]{C:old} |
---|
166 | \end{quote} |
---|
167 | Accessing any storage requires pointer arithmetic, even if it is just base-displacement addressing in an instruction. |
---|
168 | The conjoining of pointers and arrays could also be applied to structures, where a pointer references a structure field like an array element. |
---|
169 | Finally, while subscripting involves pointer arithmetic (as does a field reference @x.y.z@), the computation is complex for multi-dimensional arrays and requires array descriptors to know stride lengths along dimensions. |
---|
170 | Many C errors result from manually performing pointer arithmetic instead of using language subscripting, letting the compiler perform any arithmetic. |
---|
171 | |
---|
172 | Some C textbooks erroneously suggest manual pointer arithmetic is faster than subscripting. |
---|
173 | A sound and efficient C program does not require explicit pointer arithmetic. |
---|
174 | TODO: provide an example, explain the belief, and give modern refutation |
---|
175 | |
---|
176 | C semantics wants a programmer to \emph{believe} an array variable is a ``pointer to its first element.'' |
---|
177 | This desire becomes apparent by a detailed inspection of an array declaration. |
---|
178 | \lstinput{34-34}{bkgd-carray-arrty.c} |
---|
179 | The inspection begins by using @sizeof@ to provide program semantics for the intuition of an expression's type. |
---|
180 | An architecture with 64-bit pointer size is used, to remove irrelevant details. |
---|
181 | \lstinput{35-36}{bkgd-carray-arrty.c} |
---|
182 | Now consider the @sizeof@ expressions derived from @ar@, modified by adding pointer-to and first-element (and including unnecessary parentheses to avoid any confusion about precedence). |
---|
183 | \lstinput{37-40}{bkgd-carray-arrty.c} |
---|
184 | Given that arrays are contiguous and the size of @float@ is 4, then the size of @ar@ with 10 floats being 40 bytes is common reasoning for C programmers. |
---|
185 | Equally, C programmers know the size of a pointer to the first array element is 8. |
---|
186 | % Now, set aside for a moment the claim that this first assertion is giving information about a type. |
---|
187 | Clearly, an array and a pointer to its first element are different. |
---|
188 | |
---|
189 | In fact, the idea that there is such a thing as a pointer to an array may be surprising. |
---|
190 | It it is not the same thing as a pointer to the first element. |
---|
191 | \lstinput{42-45}{bkgd-carray-arrty.c} |
---|
192 | The first assignment generates: |
---|
193 | \begin{cfa} |
---|
194 | warning: assignment to `float (*)[10]' from incompatible pointer type `float *' |
---|
195 | \end{cfa} |
---|
196 | and the second assignment generates the opposite. |
---|
197 | |
---|
198 | The inspection now refutes any suggestion that @sizeof@ is informing about allocation rather than type information. |
---|
199 | Note, @sizeof@ has two forms, one operating on an expression and the other on a type. |
---|
200 | Using the type form yields the same results as the prior expression form. |
---|
201 | \lstinput{46-49}{bkgd-carray-arrty.c} |
---|
202 | The results are also the same when there is no allocation at all. |
---|
203 | This time, starting from a pointer-to-array type: |
---|
204 | \lstinput{51-57}{bkgd-carray-arrty.c} |
---|
205 | Hence, in all cases, @sizeof@ is reporting on type information. |
---|
206 | |
---|
207 | Therefore, thinking of an array as a pointer to its first element is too simplistic an analogue and it is not backed up by the type system. |
---|
208 | This misguided analogue works for a single-dimension array but there is no advantage other than possibly teaching beginner programmers about basic runtime array-access. |
---|
209 | |
---|
210 | Continuing, there is a short form for declaring array variables using length information provided implicitly by an initializer. |
---|
211 | \lstinput{59-62}{bkgd-carray-arrty.c} |
---|
212 | The compiler counts the number of initializer elements and uses this value as the first dimension. |
---|
213 | Unfortunately, the implicit element counting does not extend to dimensions beyond the first. |
---|
214 | \lstinput{64-67}{bkgd-carray-arrty.c} |
---|
215 | |
---|
216 | My observation is recognizing: |
---|
217 | \begin{itemize}[leftmargin=*,itemsep=0pt] |
---|
218 | \item There is value in using a type that knows its size. |
---|
219 | \item The type pointer to the (first) element does not. |
---|
220 | \item C \emph{has} a type that knows the whole picture: array, \eg @T[10]@. |
---|
221 | \item This type has all the usual derived forms, which also know the whole picture. |
---|
222 | A noteworthy example is pointer to array, \eg @T (*)[10]@. |
---|
223 | \end{itemize} |
---|
224 | |
---|
225 | |
---|
226 | \subsection{Arrays decay and pointers diffract} |
---|
227 | |
---|
228 | The last section established the difference among these four types: |
---|
229 | \lstinput{3-6}{bkgd-carray-decay.c} |
---|
230 | But the expression used for obtaining the pointer to the first element is pedantic. |
---|
231 | The root of all C programmer experience with arrays is the shortcut |
---|
232 | \lstinput{8-8}{bkgd-carray-decay.c} |
---|
233 | which reproduces @pa0@, in type and value: |
---|
234 | \lstinput{9-9}{bkgd-carray-decay.c} |
---|
235 | The validity of this initialization is unsettling, in the context of the facts established in \VRef{s:Array}. |
---|
236 | Notably, it initializes name @pa0x@ from expression @ar@, when they are not of the same type: |
---|
237 | \lstinput{10-10}{bkgd-carray-decay.c} |
---|
238 | |
---|
239 | So, C provides an implicit conversion from @float[10]@ to @float *@. |
---|
240 | \begin{quote} |
---|
241 | Except when it is the operand of the @sizeof@ operator, or the unary @&@ operator, or is a string literal used to |
---|
242 | initialize an array an expression that has type ``array of \emph{type}'' is converted to an expression with type |
---|
243 | ``pointer to \emph{type}'' that points to the initial element of the array object~\cite[\S~6.3.2.1.3]{C11} |
---|
244 | \end{quote} |
---|
245 | This phenomenon is the famous \newterm{pointer decay}, which is a decay of an array-typed expression into a pointer-typed one. |
---|
246 | It is worthy to note that the list of exceptional cases does not feature the occurrence of @ar@ in @ar[i]@. |
---|
247 | Thus, subscripting happens on pointers not arrays. |
---|
248 | |
---|
249 | Subscripting proceeds first with pointer decay, if needed. Next, \cite[\S~6.5.2.1.2]{C11} explains that @ar[i]@ is treated as if it were @(*((a)+(i)))@. |
---|
250 | \cite[\S~6.5.6.8]{C11} explains that the addition, of a pointer with an integer type, is defined only when the pointer refers to an element that is in an array, with a meaning of ``@i@ elements away from,'' which is valid if @ar@ is big enough and @i@ is small enough. |
---|
251 | Finally, \cite[\S~6.5.3.2.4]{C11} explains that the @*@ operator's result is the referenced element. |
---|
252 | Taken together, these rules illustrate that @ar[i]@ and @i[a]@ mean the same thing! |
---|
253 | |
---|
254 | Subscripting a pointer when the target is standard-inappropriate is still practically well-defined. |
---|
255 | While the standard affords a C compiler freedom about the meaning of an out-of-bound access, or of subscripting a pointer that does not refer to an array element at all, |
---|
256 | the fact that C is famously both generally high-performance, and specifically not bound-checked, leads to an expectation that the runtime handling is uniform across legal and illegal accesses. |
---|
257 | Moreover, consider the common pattern of subscripting on a @malloc@ result: |
---|
258 | \begin{cfa} |
---|
259 | float * fs = malloc( 10 * sizeof(float) ); |
---|
260 | fs[5] = 3.14; |
---|
261 | \end{cfa} |
---|
262 | The @malloc@ behaviour is specified as returning a pointer to ``space for an object whose size is'' as requested (\cite[\S~7.22.3.4.2]{C11}). |
---|
263 | But \emph{nothing} more is said about this pointer value, specifically that its referent might \emph{be} an array allowing subscripting. |
---|
264 | |
---|
265 | Under this assumption, a pointer being subscripted (or added to, then dereferenced) by any value (positive, zero, or negative), gives a view of the program's entire address space, centred around the @p@ address, divided into adjacent @sizeof(*p)@ chunks, each potentially (re)interpreted as @typeof(*p)@. |
---|
266 | I call this phenomenon \emph{array diffraction}, which is a diffraction of a single-element pointer into the assumption that its target is in the middle of an array whose size is unlimited in both directions. |
---|
267 | No pointer is exempt from array diffraction. |
---|
268 | No array shows its elements without pointer decay. |
---|
269 | |
---|
270 | A further pointer--array confusion, closely related to decay, occurs in parameter declarations. |
---|
271 | \cite[\S~6.7.6.3.7]{C11} explains that when an array type is written for a parameter, |
---|
272 | the parameter's type becomes a type that can be summarized as the array-decayed type. |
---|
273 | The respective handling of the following two parameter spellings shows that the array and pointer versions are identical. |
---|
274 | \lstinput{12-16}{bkgd-carray-decay.c} |
---|
275 | As the @sizeof(x)@ meaning changed, compared with when run on a similarly-spelled local variable declaration, |
---|
276 | @gcc@ also gives this code the warning for the first assertion: |
---|
277 | \begin{cfa} |
---|
278 | warning: 'sizeof' on array function parameter 'x' will return size of 'float *' |
---|
279 | \end{cfa} |
---|
280 | The caller of such a function is left with the reality that a pointer parameter is a pointer, no matter how it is spelled: |
---|
281 | \lstinput{18-21}{bkgd-carray-decay.c} |
---|
282 | This fragment gives the warning for the first argument, in the second call. |
---|
283 | \begin{cfa} |
---|
284 | warning: 'f' accessing 40 bytes in a region of size 4 |
---|
285 | \end{cfa} |
---|
286 | |
---|
287 | The shortened parameter syntax @T x[]@ is a further way to spell ``pointer.'' |
---|
288 | Note the opposite meaning of this spelling now, compared with its use in local variable declarations. |
---|
289 | This point of confusion is illustrated in: |
---|
290 | \lstinput{23-30}{bkgd-carray-decay.c} |
---|
291 | Note, \CC gives a warning for the initialization of @cp@. |
---|
292 | \begin{cfa} |
---|
293 | warning: ISO C++ forbids converting a string constant to 'char*' |
---|
294 | \end{cfa} |
---|
295 | and C gives a warning at the call of @edit@, if @const@ is added to the declaration of @cp@. |
---|
296 | \begin{cfa} |
---|
297 | warning: passing argument 1 of 'edit' discards 'const' qualifier from pointer target type |
---|
298 | \end{cfa} |
---|
299 | The basic two meanings, with a syntactic difference helping to distinguish, are illustrated in the declarations of @ca@ \vs @cp@, whose subsequent @edit@ calls behave differently. |
---|
300 | The syntax-caused confusion is in the comparison of the first and last lines, both of which use a literal to initialize an object declared with spelling @T x[]@. |
---|
301 | But these initialized declarations get opposite meanings, depending on whether the object is a local variable or a parameter! |
---|
302 | |
---|
303 | In summary, when a function is written with an array-typed parameter, |
---|
304 | \begin{itemize}[leftmargin=*] |
---|
305 | \item an appearance of passing an array by value is always an incorrect understanding, |
---|
306 | \item a dimension value, if any is present, is ignored, |
---|
307 | \item pointer decay is forced at the call site and the callee sees the parameter having the decayed type. |
---|
308 | \end{itemize} |
---|
309 | |
---|
310 | Pointer decay does not affect pointer-to-array types, because these are already pointers, not arrays. |
---|
311 | As a result, a function with a pointer-to-array parameter sees the parameter exactly as the caller does: |
---|
312 | \par\noindent |
---|
313 | \begin{tabular}{@{\hspace*{-0.75\parindentlnth}}l@{}l@{}} |
---|
314 | \lstinput{32-36}{bkgd-carray-decay.c} |
---|
315 | & |
---|
316 | \lstinput{38-42}{bkgd-carray-decay.c} |
---|
317 | \end{tabular} |
---|
318 | \par\noindent |
---|
319 | \VRef[Table]{bkgd:ar:usr:decay-parm} gives the reference for the decay phenomenon seen in parameter declarations. |
---|
320 | |
---|
321 | \begin{table} |
---|
322 | \caption{Syntactic Reference for Decay during Parameter-Passing. |
---|
323 | Includes interaction with \lstinline{const}ness, where ``immutable'' refers to a restriction on the callee's ability.} |
---|
324 | \label{bkgd:ar:usr:decay-parm} |
---|
325 | \centering |
---|
326 | \begin{tabular}{llllll} |
---|
327 | & Description & Type & Parameter Declaration & \CFA \\ |
---|
328 | \hline |
---|
329 | & & & @T * x,@ & @* T x,@ \\ |
---|
330 | $\triangleright$ & pointer to value & @T *@ & @T x[10],@ & @[10] T x,@ \\ |
---|
331 | & & & @T x[],@ & @[] T x,@ \\ |
---|
332 | \hline |
---|
333 | & & & @T * const x,@ & @const * T x@ \\ |
---|
334 | & immutable ptr.\ to val. & @T * const@ & @T x[const 10],@ & @[const 10] T x,@ \\ |
---|
335 | & & & @T x[const],@ & @[const] T x,@\\ |
---|
336 | \hline |
---|
337 | & & & @const T * x,@ & @ * const T x,@ \\ |
---|
338 | & & & @T const * x,@ & @ * T const x,@ \\ |
---|
339 | & ptr.\ to immutable val. & @const T *@ & @const T x[10],@ & @[10] const T x,@ \\ |
---|
340 | & & @T const *@ & @T const x[10],@ & @[10] T const x,@ \\ |
---|
341 | & & & @const T x[],@ & @[] const T x,@ \\ |
---|
342 | & & & @T const x[],@ & @[] T const x,@ \\ |
---|
343 | \hline \hline |
---|
344 | & & & @T (*x)[10],@ & @* [10] T x,@ \\ |
---|
345 | $\triangleright$ & ptr.\ to ar.\ of val. & @T(*)[10]@ & @T x[3][10],@ & @[3][10] T x,@ \\ |
---|
346 | & & & @T x[][10],@ & @[][10] T x,@ \\ |
---|
347 | \hline |
---|
348 | & & & @T ** x,@ & @** T x,@ \\ |
---|
349 | & ptr.\ to ptr.\ to val. & @T **@ & @T * x[10],@ & @[10] * T x,@ \\ |
---|
350 | & & & @T * x[],@ & @[] * T x,@ \\ |
---|
351 | \hline |
---|
352 | & ptr.\ to ptr.\ to imm.\ val. & @const char **@ & @const char * argv[],@ & @[] * const char argv,@ \\ |
---|
353 | & & & \emph{others elided} & \emph{others elided} \\ |
---|
354 | \hline |
---|
355 | \end{tabular} |
---|
356 | \end{table} |
---|
357 | |
---|
358 | |
---|
359 | \subsection{Variable Length Arrays} |
---|
360 | |
---|
361 | As of C99, the C standard supports a \newterm{variable length array} (VLA)~\cite[\S~6.7.5.2.5]{C99}, providing a dynamic-fixed array feature \see{\VRef{s:ArrayIntro}}. |
---|
362 | Note, the \CC standard does not support VLAs, but @g++@ provides them. |
---|
363 | A VLA is used when the desired number of array elements is \emph{unknown} at compile time. |
---|
364 | \begin{cfa} |
---|
365 | size_t cols; |
---|
366 | scanf( "%d", &cols ); |
---|
367 | double ar[cols]; |
---|
368 | \end{cfa} |
---|
369 | The array dimension is read from outside the program and used to create an array of size @cols@ on the stack. |
---|
370 | The VLA is implemented by the @alloca@ routine, which bumps the stack pointer. |
---|
371 | Unfortunately, there is significant misinformation about VLAs, \eg the stack size is limited (small), or VLAs cause stack failures or are inefficient. |
---|
372 | VLAs exist as far back as Algol W~\cite[\S~5.2]{AlgolW} and are a sound and efficient data type. |
---|
373 | For types with a dynamic-fixed stack, \eg coroutines or user-level threads, large VLAs can overflow the stack without appropriately sizing the stack, so heap allocation is used when the array size is unbounded. |
---|
374 | |
---|
375 | |
---|
376 | \subsection{Multidimensional Arrays} |
---|
377 | \label{toc:mdimpl} |
---|
378 | |
---|
379 | % TODO: introduce multidimensional array feature and approaches |
---|
380 | |
---|
381 | When working with arrays, \eg linear algebra, array dimensions are referred to as ``rows'' and ``columns'' for a matrix, adding ``planes'' for a cube. |
---|
382 | (There is little terminology for higher dimensional arrays.) |
---|
383 | For example, an acrostic poem\footnote{A type of poetry where the first, last or other letters in a line spell out a particular word or phrase in a vertical column.} |
---|
384 | can be treated as a grid of characters, where the rows are the text and the columns are the embedded keyword(s). |
---|
385 | Within a poem, there is the concept of a \newterm{slice}, \eg a row is a slice for the poem text, a column is a slice for a keyword. |
---|
386 | In general, the dimensioning and subscripting for multidimensional arrays has two syntactic forms: @m[r,c]@ or @m[r][c]@. |
---|
387 | |
---|
388 | Commonly, an array, matrix, or cube, is visualized (especially in mathematics) as a contiguous row, rectangle, or block. |
---|
389 | This conceptualization is reenforced by subscript ordering, \eg $m_{r,c}$ for a matrix and $c_{p,r,c}$ for a cube. |
---|
390 | Few programming languages differ from the mathematical subscript ordering. |
---|
391 | However, computer memory is flat, and hence, array forms are structured in memory as appropriate for the runtime system. |
---|
392 | The closest representation to the conceptual visualization is for an array object to be contiguous, and the language structures this memory using pointer arithmetic to access the values using various subscripts. |
---|
393 | This approach still has degrees of layout freedom, such as row or column major order, \ie juxtaposed rows or columns in memory, even when the subscript order remains fixed. |
---|
394 | For example, programming languages like MATLAB, Fortran, Julia and R store matrices in column-major order since they are commonly used for processing column-vectors in tabular data sets but retain row-major subscripting to match with mathematical notation. |
---|
395 | In general, storage layout is hidden by subscripting, and only appears when passing arrays among different programming languages or accessing specific hardware. |
---|
396 | |
---|
397 | \VRef[Figure]{f:FixedVariable} shows two C90 approaches for manipulating a contiguous matrix. |
---|
398 | Note, C90 does not support VLAs. |
---|
399 | The fixed-dimension approach (left) uses the type system; |
---|
400 | however, it requires all dimensions except the first to be specified at compile time, \eg @m[][6]@, allowing all subscripting stride calculations to be generated with constants. |
---|
401 | Hence, every matrix passed to @fp1@ must have exactly 6 columns but the row size can vary. |
---|
402 | The variable-dimension approach (right) ignores (violates) the type system, \ie argument and parameters types do not match, and subscripting is performed manually using pointer arithmetic in the macro @sub@. |
---|
403 | |
---|
404 | \begin{figure} |
---|
405 | \begin{tabular}{@{}l@{\hspace{40pt}}l@{}} |
---|
406 | \multicolumn{1}{c}{\textbf{Fixed Dimension}} & \multicolumn{1}{c}{\textbf{Variable Dimension}} \\ |
---|
407 | \begin{cfa} |
---|
408 | |
---|
409 | void fp1( int rows, int m[][@6@] ) { |
---|
410 | ... printf( "%d ", @m[r][c]@ ); ... |
---|
411 | } |
---|
412 | int fm1[4][@6@], fm2[6][@6@]; // no VLA |
---|
413 | // initialize matrixes |
---|
414 | fp1( 4, fm1 ); // implicit 6 columns |
---|
415 | fp1( 6, fm2 ); |
---|
416 | \end{cfa} |
---|
417 | & |
---|
418 | \begin{cfa} |
---|
419 | #define sub( m, r, c ) *(m + r * sizeof( m[0] ) + c) |
---|
420 | void fp2( int rows, int cols, int *m ) { |
---|
421 | ... printf( "%d ", @sub( m, r, c )@ ); ... |
---|
422 | } |
---|
423 | int vm1[@4@][@4@], vm2[@6@][@8@]; // no VLA |
---|
424 | // initialize matrixes |
---|
425 | fp2( 4, 4, vm1 ); |
---|
426 | fp2( 6, 8, vm2 ); |
---|
427 | \end{cfa} |
---|
428 | \end{tabular} |
---|
429 | \caption{C90 Fixed \vs Variable Contiguous Matrix Styles} |
---|
430 | \label{f:FixedVariable} |
---|
431 | \end{figure} |
---|
432 | |
---|
433 | Many languages allow multidimensional arrays-of-arrays, \eg in Pascal or \CC. |
---|
434 | \begin{cquote} |
---|
435 | \begin{tabular}{@{}ll@{}} |
---|
436 | \begin{pascal} |
---|
437 | var m : array[0..4, 0..4] of Integer; (* matrix *) |
---|
438 | type AT = array[0..4] of Integer; (* array type *) |
---|
439 | type MT = array[0..4] of AT; (* array of array type *) |
---|
440 | var aa : MT; (* array of array variable *) |
---|
441 | m@[1][2]@ := 1; aa@[1][2]@ := 1 (* same subscripting *) |
---|
442 | \end{pascal} |
---|
443 | & |
---|
444 | \begin{c++} |
---|
445 | int m[5][5]; |
---|
446 | |
---|
447 | typedef vector< vector<int> > MT; |
---|
448 | MT vm( 5, vector<int>( 5 ) ); |
---|
449 | m@[1][2]@ = 1; aa@[1][2]@ = 1; |
---|
450 | \end{c++} |
---|
451 | \end{tabular} |
---|
452 | \end{cquote} |
---|
453 | The language decides if the matrix and array-of-array are laid out the same or differently. |
---|
454 | For example, an array-of-array may be an array of row pointers to arrays of columns, so the rows may not be contiguous in memory nor even the same length (triangular matrix). |
---|
455 | Regardless, there is usually a uniform subscripting syntax masking the memory layout, even though a language could differentiated between the two forms using subscript syntax, \eg @m[1,2]@ \vs @aa[1][2]@. |
---|
456 | Nevertheless, controlling memory layout can make a difference in what operations are allowed and in performance (caching/NUMA effects). |
---|
457 | |
---|
458 | C also provides non-contiguous arrays-of-arrays. |
---|
459 | \begin{cfa} |
---|
460 | int m[5][5]; $\C{// contiguous}$ |
---|
461 | int * aa[5]; $\C{// non-contiguous}$ |
---|
462 | \end{cfa} |
---|
463 | both with different memory layout using the same subscripting, and both with different degrees of issues. |
---|
464 | The focus of this work is on the contiguous multidimensional arrays in C. |
---|
465 | The reason is that programmers are often forced to use the more complex array-of-array form when a contiguous array would be simpler, faster, and safer. |
---|
466 | Nevertheless, the C array-of-array form is still important for special circumstances. |
---|
467 | |
---|
468 | \VRef[Figure]{f:ContiguousNon-contiguous} shows a powerful extension made in C99 for manipulating contiguous \vs non-contiguous arrays.\footnote{C90 also supported non-contiguous arrays.} |
---|
469 | For contiguous-array (including VLA) arguments, C99 conjoins one or more of the parameters as a downstream dimension(s), \eg @cols@, implicitly using this parameter to compute the row stride of @m@. |
---|
470 | There is now sufficient information to support subscript checking along the columns to prevent buffer-overflow problems, but subscript checking is not provided. |
---|
471 | If the declaration of @fc@ is changed to: |
---|
472 | \begin{cfa} |
---|
473 | void fc( int rows, int cols, int m[@rows@][@cols@] ) ... |
---|
474 | \end{cfa} |
---|
475 | it is possible for C to perform bound checking across all subscripting. |
---|
476 | While this contiguous-array capability is a step forward, it is still the programmer's responsibility to manually manage the number of dimensions and their sizes, both at the function definition and call sites. |
---|
477 | That is, the array does not automatically carry its structure and sizes for use in computing subscripts. |
---|
478 | While the non-contiguous style in @faa@ looks very similar to @fc@, the compiler only understands the unknown-sized array of row pointers, and it relies on the programmer to traverse the columns in a row correctly with a correctly bounded loop index. |
---|
479 | Specifically, there is no requirement that the rows are the same length, like a poem with different length lines. |
---|
480 | |
---|
481 | \begin{figure} |
---|
482 | \begin{tabular}{@{}ll@{}} |
---|
483 | \multicolumn{1}{c}{\textbf{Contiguous}} & \multicolumn{1}{c}{\textbf{ Non-contiguous}} \\ |
---|
484 | \begin{cfa} |
---|
485 | void fc( int rows, @int cols@, int m[ /* rows */ ][@cols@] ) { |
---|
486 | for ( size_t r = 0; r < rows; r += 1 ) { |
---|
487 | for ( size_t c = 0; c < cols; c += 1 ) |
---|
488 | ... @m[r][c]@ ... |
---|
489 | } |
---|
490 | int m@[5][5]@; |
---|
491 | for ( int r = 0; r < 5; r += 1 ) { |
---|
492 | |
---|
493 | for ( int c = 0; c < 5; c += 1 ) |
---|
494 | m[r][c] = r + c; |
---|
495 | } |
---|
496 | fc( 5, 5, m ); |
---|
497 | \end{cfa} |
---|
498 | & |
---|
499 | \begin{cfa} |
---|
500 | void faa( int rows, int cols, int * m[ @/* cols */@ ] ) { |
---|
501 | for ( size_t r = 0; r < rows; r += 1 ) { |
---|
502 | for ( size_t c = 0; c < cols; c += 1 ) |
---|
503 | ... @m[r][c]@ ... |
---|
504 | } |
---|
505 | int @* aa[5]@; // row pointers |
---|
506 | for ( int r = 0; r < 5; r += 1 ) { |
---|
507 | @aa[r] = malloc( 5 * sizeof(int) );@ // create rows |
---|
508 | for ( int c = 0; c < 5; c += 1 ) |
---|
509 | aa[r][c] = r + c; |
---|
510 | } |
---|
511 | faa( 5, 5, aa ); |
---|
512 | \end{cfa} |
---|
513 | \end{tabular} |
---|
514 | \caption{C99 Contiguous \vs Non-contiguous Matrix Styles} |
---|
515 | \label{f:ContiguousNon-contiguous} |
---|
516 | \end{figure} |
---|
517 | |
---|
518 | |
---|
519 | \subsection{Multi-dimensional arrays decay and pointers diffract} |
---|
520 | |
---|
521 | As for single-dimension arrays, multi-dimensional arrays have similar issues. |
---|
522 | \lstinput{16-18}{bkgd-carray-mdim.c} |
---|
523 | The significant axis of deriving expressions from @ar@ is now ``itself,'' ``first element'' or ``first grand-element (meaning, first element of first element).'' |
---|
524 | \PAB{Explain, explain, explain.} |
---|
525 | \lstinput{20-26}{bkgd-carray-mdim.c} |
---|
526 | \PAB{Explain, explain, explain.} |
---|
527 | \lstinput{28-36}{bkgd-carray-mdim.c} |
---|
528 | \PAB{Explain, explain, explain.} |
---|
529 | \lstinput{38-44}{bkgd-carray-mdim.c} |
---|
530 | |
---|
531 | |
---|
532 | \subsection{Array Parameter Declaration} |
---|
533 | |
---|
534 | Passing an array as an argument to a function is necessary. |
---|
535 | Assume a parameter is an array when the function intends to subscript it. |
---|
536 | This section asserts that a more satisfactory/formal characterization does not exist in C, surveys the ways that C API authors communicate ``@p@ has zero or more dimensions'' and calls out the minority cases where the C type system is using or verifying such claims. |
---|
537 | |
---|
538 | A C parameter declarations look different, from the caller's and callee's perspectives. |
---|
539 | Both perspectives consist of the text read by a programmer and the semantics enforced by the type system. |
---|
540 | The caller's perspective is available from a function declaration, which allow definition-before-use and separate compilation, but can also be read from (the non-body part of) a function definition. |
---|
541 | The callee's perspective is what is available inside the function. |
---|
542 | \begin{cfa} |
---|
543 | int foo( int, float, char ); $\C{// declaration, names optional}$ |
---|
544 | int bar( int i, float f, char c ) { $\C{// definition, names mandatory}$ |
---|
545 | // caller's perspective of foo; callee's perspective of bar |
---|
546 | } |
---|
547 | // caller's perspectives of foo's and bar's |
---|
548 | \end{cfa} |
---|
549 | In caller's perspective, the parameter names (by virtue of being optional) are really comments; |
---|
550 | in the callee's perspective, parameter names are semantically significant. |
---|
551 | Array parameters introduce a further, subtle, semantic difference and considerable freedom to comment. |
---|
552 | |
---|
553 | At the semantic level, there is no such thing as an array parameter, except for one case (@T [static 5]@) discussed shortly. |
---|
554 | Rather, there are only pointer parameters. |
---|
555 | This fact probably shares considerable responsibility for the common sense of ``an array is just a pointer,'' which has been refuted in non-parameter contexts. |
---|
556 | This fact holds in both the caller's and callee's perspectives. |
---|
557 | However, a parameter's type can include ``array of.'', \eg the type ``pointer to array of 5 ints'' (@T (*)[5]@) is a pointer type. |
---|
558 | This type is fully meaningful in the sense that its description does not contain any information that the type system ignores, and the type appears the same in the caller's \vs callee's perspectives. |
---|
559 | In fact, the outermost type constructor (syntactically first dimension) is really the one that determines the flavour of parameter. |
---|
560 | |
---|
561 | Yet, C allows array syntax for the outermost type constructor, from which comes the freedom to comment. |
---|
562 | An array parameter declaration can specify the outermost dimension with a dimension value, @[10]@ (which is ignored), an empty dimension list, @[ ]@, or a pointer, @*@, as seen in \VRef[Figure]{f:ArParmEquivDecl}. |
---|
563 | The rationale for rejecting the first ``invalid'' row follows shortly, while the second ``invalid'' row is simple nonsense, included to complete the pattern; its syntax hints at what the final row actually achieves. |
---|
564 | |
---|
565 | \begin{figure} |
---|
566 | \begin{cquote} |
---|
567 | \begin{tabular}{@{}llll@{}} |
---|
568 | \begin{cfa} |
---|
569 | float sum( float a[5] ); |
---|
570 | float sum( float a[5][4] ); |
---|
571 | float sum( float a[5][] ); |
---|
572 | float sum( float a[5]* ); |
---|
573 | float sum( float *a[5] ); |
---|
574 | \end{cfa} |
---|
575 | & |
---|
576 | \begin{cfa} |
---|
577 | float sum( float a[] ); |
---|
578 | float sum( float a[][4] ); |
---|
579 | float sum( float a[][] ); |
---|
580 | float sum( float a[]* ); |
---|
581 | float sum( float *a[] ); |
---|
582 | \end{cfa} |
---|
583 | & |
---|
584 | \begin{cfa} |
---|
585 | float sum( float *a ); |
---|
586 | float sum( float (*a)[4] ); |
---|
587 | float sum( float (*a)[] ); |
---|
588 | float sum( float (*a)* ); |
---|
589 | float sum( float **a ); |
---|
590 | \end{cfa} |
---|
591 | & |
---|
592 | \begin{cfa} |
---|
593 | // ar of float |
---|
594 | // mat of float |
---|
595 | // invalid |
---|
596 | // invalid |
---|
597 | // ar of ptr to float |
---|
598 | \end{cfa} |
---|
599 | \end{tabular} |
---|
600 | \end{cquote} |
---|
601 | \caption{Multiple ways to declare an array parameter. |
---|
602 | Across a valid row, every declaration is equivalent. |
---|
603 | Each column gives a declaration style, where the style for that column is read from the first row. |
---|
604 | The second row begins the style for multiple dimensions, with the rows thereafter providing context for the choice of which second-row \lstinline{[]} receives the column-style variation.} |
---|
605 | \label{f:ArParmEquivDecl} |
---|
606 | \end{figure} |
---|
607 | |
---|
608 | In the leftmost style, the typechecker ignores the actual value in most practical cases. |
---|
609 | This value is allowed to be a dynamic expression, and then it has practical cases. |
---|
610 | \begin{cfa} |
---|
611 | void foo( int @n@ ) { |
---|
612 | float _42( float @a[n]@ ) { // nested function |
---|
613 | a[0] = 42; |
---|
614 | } |
---|
615 | float b[n]; |
---|
616 | _42( b ); |
---|
617 | } |
---|
618 | \end{cfa} |
---|
619 | |
---|
620 | |
---|
621 | % To help contextualize the matrix part of this example, the syntaxes @float [5][]@, @float [][]@ and @float (*)[]@ are all rejected, for reasons discussed shortly. |
---|
622 | % So are @float[5]*@, @float[]*@ and @float (*)*@. These latter ones are simply nonsense, though they hint at ``1d array of pointers'', whose equivalent syntax options are, @float *[5]@, @float *[]@, and @float **@. |
---|
623 | |
---|
624 | It is a matter of taste as to whether a programmer should use a form as far left as possible (getting the most out of possible subscripting and dimension sizes), sticking to the right (avoiding false comfort from suggesting the typechecker is checking more than it is), or compromising in the middle (reducing unchecked information, yet clearly stating, ``I will subscript). |
---|
625 | |
---|
626 | Note that this equivalence of pointer and array declarations is special to parameters. |
---|
627 | It does not apply to local variables, where true array declarations are possible. |
---|
628 | \begin{cfa} |
---|
629 | void f( float * a ) { |
---|
630 | float * b = a; // ok |
---|
631 | float c[] = a; // reject |
---|
632 | float d[] = { 1.0, 2.0, 3.0 }; // ok |
---|
633 | static_assert( sizeof(b) == sizeof(float*) ); |
---|
634 | static_assert( sizeof(d) != sizeof(float*) ); |
---|
635 | } |
---|
636 | \end{cfa} |
---|
637 | This equivalence has the consequence that the type system does not help a caller get it right. |
---|
638 | \begin{cfa} |
---|
639 | float sum( float v[] ); |
---|
640 | float arg = 3.14; |
---|
641 | sum( &arg ); $\C{// accepted, v = \&arg}$ |
---|
642 | \end{cfa} |
---|
643 | |
---|
644 | Given the syntactic dimension forms @[ ]@ or @[5]@, it raises the question of qualifying the implied array pointer rather than the array element type. |
---|
645 | For example, the qualifiers after the @*@ apply to the array pointer. |
---|
646 | \begin{cfa} |
---|
647 | void foo( const volatile int * @const volatile@ ); |
---|
648 | void foo( const volatile int [ ] @const volatile@ ); // does not parse |
---|
649 | \end{cfa} |
---|
650 | C instead puts these pointer qualifiers syntactically into the first dimension. |
---|
651 | \begin{cquote} |
---|
652 | @[@ \textit{type-qualifier-list}$_{opt}$ \textit{assignment-expression}$_{opt}$ @]@ |
---|
653 | \end{cquote} |
---|
654 | \begin{cfa} |
---|
655 | void foo( int [@const volatile@] ); |
---|
656 | void foo( int [@const volatile@ 5] ); $\C{// 5 is ignored}$ |
---|
657 | \end{cfa} |
---|
658 | |
---|
659 | To make the first dimension size meaningful, C adds this form. |
---|
660 | \begin{cquote} |
---|
661 | @[@ @static@ \textit{type-qualifier-list}$_{opt}$ \textit{assignment-expression} @]@ |
---|
662 | \end{cquote} |
---|
663 | \begin{cfa} |
---|
664 | void foo( int [static @3@] ); |
---|
665 | int ar[@10@]; |
---|
666 | foo( ar ); // check argument dimension 10 > 3 |
---|
667 | \end{cfa} |
---|
668 | Here, the @static@ storage qualifier defines the minimum array size for its argument. |
---|
669 | @gcc@ ignores this dimension qualifier, \ie it gives no warning if the argument array size is less than the parameter minimum. However, @clang@ implements the check, in accordance with the standard. TODO: be specific about versions |
---|
670 | |
---|
671 | Note that there are now two different meanings for modifiers in the same position. In |
---|
672 | \begin{cfa} |
---|
673 | void foo( int x[static const volatile 3] ); |
---|
674 | \end{cfa} |
---|
675 | the @static@ applies to the 3, while the @const volatile@ applies to the @x@. |
---|
676 | |
---|
677 | With multidimensional arrays, on dimensions after the first, a size is required and, is not ignored. |
---|
678 | These sizes are required for the callee to be able to subscript. |
---|
679 | \begin{cfa} |
---|
680 | void f( float a[][10], float b[][100] ) { |
---|
681 | static_assert( ((char*)&a([1])) - ((char*)&a([0])) == 10 * sizeof(float) ); |
---|
682 | static_assert( ((char*)&b([1])) - ((char*)&b([0])) == 100 * sizeof(float) ); |
---|
683 | } |
---|
684 | \end{cfa} |
---|
685 | Here, the distance between the first and second elements of each array depends on the inner dimension size. |
---|
686 | |
---|
687 | This significance of an inner dimension's length is a fact of the callee's perspective. |
---|
688 | In the caller's perspective, the type sytem is quite lax. |
---|
689 | Here, there is (some, but) little checking that what is being passed, matches. |
---|
690 | % void f( float [][10] ); |
---|
691 | % int n = 100; |
---|
692 | % float a[100], b[n]; |
---|
693 | % f(&a); // reject |
---|
694 | % f(&b); // accept |
---|
695 | \begin{cfa} |
---|
696 | void foo() { |
---|
697 | void f( float [][10] ); |
---|
698 | int n = 100; |
---|
699 | float a[100], b[3][12], c[n], d[n][n]; |
---|
700 | f( a ); |
---|
701 | f( b ); $\C{// reject: inner dimension 12 for 10}$ |
---|
702 | f( c ); |
---|
703 | f( @d@ ); $\C{// accept with inner dimension n for 10}$ |
---|
704 | f( &a ); $\C{// reject: inner dimension 100 for 10}$ |
---|
705 | f( &b ); |
---|
706 | f( @&c@ ); $\C{// accept with inner dimension n for 10}$ |
---|
707 | f( &d ); |
---|
708 | } |
---|
709 | \end{cfa} |
---|
710 | The cases without comments are rejections, but simply because the array ranks do not match; in the commented cases, the ranks match and the rules being discussed apply. |
---|
711 | The cases @f(b)@ and @f(&a)@ show where some length checking occurs. |
---|
712 | But this checking misses the cases @f(d)@ and @f(&c)@, allowing the calls with mismatched lengths, actually 100 for 10. |
---|
713 | The C checking rule avoids false alarms, at the expense of safety, by allowing any combinations that involve dynamic values. |
---|
714 | Ultimately, an inner dimension's size is a callee's \emph{assumption} because the type system uses declaration details in the callee's perspective that it does not enforce in the caller's perspective. |
---|
715 | |
---|
716 | Finally, to handle higher-dimensional VLAs, C repurposed the @*@ \emph{within} the dimension in a declaration to mean that the callee has make an assumption about the size, but no (unchecked, possibly wrong) information about this assumption is included for the caller-programmer's benefit/over-confidence. |
---|
717 | \begin{cquote} |
---|
718 | @[@ \textit{type-qualifier-list$_{opt}$} @* ]@ |
---|
719 | \end{cquote} |
---|
720 | \begin{cfa} |
---|
721 | void foo( float [][@*@] ); $\C{// declaration}$ |
---|
722 | void foo( float a[][10] ) { ... } $\C{// definition}$ |
---|
723 | \end{cfa} |
---|
724 | Repeating it with the full context of a VLA is useful: |
---|
725 | \begin{cfa} |
---|
726 | void foo( int, float [][@*@] ); $\C{// declaration}$ |
---|
727 | void foo( int n, float a[][n] ) { ... } $\C{// definition}$ |
---|
728 | \end{cfa} |
---|
729 | Omitting the dimension from the declaration is consistent with omitting parameter names, for the declaration case has no name @n@ in scope. |
---|
730 | The omission is also redacting all information not needed to generate correct caller-side code. |
---|
731 | |
---|
732 | |
---|
733 | \subsection{Arrays could be values} |
---|
734 | |
---|
735 | All arrays have a know runtime size at their point of declaration. |
---|
736 | Furthermore, C provides an explicit mechanism to pass an array's dimensions to a function. |
---|
737 | Nevertheless, an array cannot be copied, and hence, not passed by value to a function, even when there is sufficient information to do so. |
---|
738 | |
---|
739 | However, if an array is a structure field (compile-time size), it can be copied and passed by value. |
---|
740 | For example, a C @jmp_buf@ is an array. |
---|
741 | \begin{cfa} |
---|
742 | typedef long int jmp_buf[8]; |
---|
743 | \end{cfa} |
---|
744 | A instance of this array can be declared as a structure field. |
---|
745 | \begin{cfa} |
---|
746 | struct Jmp_Buf { |
---|
747 | @jmp_buf@ jb; |
---|
748 | }; |
---|
749 | \end{cfa} |
---|
750 | Now the array can be copied (and passed by value) because structures can be copied. |
---|
751 | \begin{cfa} |
---|
752 | Jmp_Buf jb1, jb2; |
---|
753 | jb1 = jb2; |
---|
754 | void foo( Jmp_Buf ); |
---|
755 | foo( jb2 ); |
---|
756 | \end{cfa} |
---|
757 | |
---|
758 | This same argument applies to returning arrays from functions. |
---|
759 | There can be sufficient information to return an array by value but it is not supported. |
---|
760 | Again, array wrapping allows an array to be returned from a function and copied into variable. |
---|
761 | |
---|
762 | |
---|
763 | \section{Linked List} |
---|
764 | |
---|
765 | Linked-lists are blocks of storage connected using one or more pointers. |
---|
766 | The storage block (node) is logically divided into data (user payload) and links (list pointers), where the links are the only component used by the list structure. |
---|
767 | Since the data is opaque, list structures are often polymorphic over the data, which is often homogeneous. |
---|
768 | |
---|
769 | The links organize nodes into a particular format, \eg queue, tree, hash table, \etc, with operations specific to that format. |
---|
770 | Because a node's existence is independent of the data structure that organizes it, all nodes are manipulated by address not value; |
---|
771 | hence, all data structure routines take and return pointers to nodes and not the nodes themselves. |
---|
772 | |
---|
773 | |
---|
774 | \subsection{Design issues} |
---|
775 | \label{toc:lst:issue} |
---|
776 | |
---|
777 | This thesis focuses on a reduced design space for linked lists that target \emph{system programmers}. |
---|
778 | Within this restricted space, all design-issue discussions assume the following invariants; |
---|
779 | alternatives to the assumptions are discussed under Future Work (Section~\ref{toc:lst:futwork}). |
---|
780 | \begin{itemize} |
---|
781 | \item A doubly-linked list is being designed. |
---|
782 | Generally, the discussed issues apply similarly for singly-linked lists. |
---|
783 | Circular \vs ordered linking is discussed under List identity (Section~\ref{toc:lst:issue:ident}). |
---|
784 | \item Link fields are system-managed. |
---|
785 | The user works with the system-provided API to query and modify list membership. |
---|
786 | The system has freedom over how to represent these links. |
---|
787 | \item The user data must provide storage for the list link-fields. |
---|
788 | Hence, a list node is \emph{statically} defined as data and links \vs a node that is \emph{dynamically} constructed from data and links \see{\VRef{toc:lst:issue:attach}}. |
---|
789 | \end{itemize} |
---|
790 | |
---|
791 | |
---|
792 | \subsection{Preexisting linked-list libraries} |
---|
793 | \label{s:PreexistingLinked-ListLibraries} |
---|
794 | |
---|
795 | Two preexisting linked-list libraries are used throughout, to show examples of the concepts being defined, |
---|
796 | and further libraries are introduced as needed. |
---|
797 | \begin{enumerate} |
---|
798 | \item Linux Queue library~\cite{lst:linuxq} (LQ) of @<sys/queue.h>@. |
---|
799 | \item \CC Standard Template Library's (STL)\footnote{The term STL is contentious as some people prefer the term standard library.} @std::list@\cite{lst:stl} |
---|
800 | \end{enumerate} |
---|
801 | %A general comparison of libraries' abilities is given under Related Work (Section~\ref{toc:lst:relwork}). |
---|
802 | For the discussion, assume the fictional type @req@ (request) is the user's payload in examples. |
---|
803 | As well, the list library is helping the user manage (organize) requests, \eg a request can be work on the level of handling a network arrival-event or scheduling a thread. |
---|
804 | |
---|
805 | |
---|
806 | \subsection{Link attachment: intrusive vs.\ wrapped} |
---|
807 | \label{toc:lst:issue:attach} |
---|
808 | |
---|
809 | Link attachment deals with the question: |
---|
810 | Where are the libraries' inter-node link-fields stored, in relation to the user's payload data fields? |
---|
811 | \VRef[Figure]{fig:lst-issues-attach} shows three basic styles. |
---|
812 | \VRef[Figure]{f:Intrusive} shows the \newterm{intrusive} style, placing the link fields inside the payload structure. |
---|
813 | \VRef[Figures]{f:WrappedRef} and \subref*{f:WrappedValue} show the two \newterm{wrapped} styles, which place the payload inside a generic library-provided structure that then defines the link fields. |
---|
814 | The wrapped style distinguishes between wrapping a reference and wrapping a value, \eg @list<req *>@ or @list<req>@. |
---|
815 | (For this discussion, @list<req &>@ is similar to @list<req *>@.) |
---|
816 | This difference is one of user style, not framework capability. |
---|
817 | Library LQ is intrusive; STL is wrapped with reference and value. |
---|
818 | |
---|
819 | \begin{comment} |
---|
820 | \begin{figure} |
---|
821 | \begin{tabularx}{\textwidth}{Y|Y|Y} |
---|
822 | \lstinput[language=C]{20-39}{lst-issues-intrusive.run.c} |
---|
823 | &\lstinputlisting[language=C++]{20-39}{lst-issues-wrapped-byref.run.cpp} |
---|
824 | &\lstinputlisting[language=C++]{20-39}{lst-issues-wrapped-emplaced.run.cpp} |
---|
825 | \\ & & |
---|
826 | \\ |
---|
827 | \includegraphics[page=1]{lst-issues-attach.pdf} |
---|
828 | & |
---|
829 | \includegraphics[page=2]{lst-issues-attach.pdf} |
---|
830 | & |
---|
831 | \includegraphics[page=3]{lst-issues-attach.pdf} |
---|
832 | \\ & & |
---|
833 | \\ |
---|
834 | (a) & (b) & (c) |
---|
835 | \end{tabularx} |
---|
836 | \caption{ |
---|
837 | Three styles of link attachment: (a)~intrusive, (b)~wrapped reference, and (c)~wrapped value. |
---|
838 | The diagrams show the memory layouts that result after the code runs, eliding the head object \lstinline{reqs}; |
---|
839 | head objects are discussed in Section~\ref{toc:lst:issue:ident}. |
---|
840 | In (a), the field \lstinline{req.x} names a list direction; |
---|
841 | these are discussed in Section~\ref{toc:lst:issue:simultaneity}. |
---|
842 | In (b) and (c), the type \lstinline{node} represents a system-internal type, |
---|
843 | which is \lstinline{std::_List_node} in the GNU implementation. |
---|
844 | (TODO: cite? found in /usr/include/c++/7/bits/stl\_list.h ) |
---|
845 | } |
---|
846 | \label{fig:lst-issues-attach} |
---|
847 | \end{figure} |
---|
848 | \end{comment} |
---|
849 | |
---|
850 | \begin{figure} |
---|
851 | \centering |
---|
852 | \newsavebox{\myboxA} % used with subfigure |
---|
853 | \newsavebox{\myboxB} |
---|
854 | \newsavebox{\myboxC} |
---|
855 | |
---|
856 | \begin{lrbox}{\myboxA} |
---|
857 | \begin{tabular}{@{}l@{}} |
---|
858 | \lstinput[language=C]{20-35}{lst-issues-intrusive.run.c} \\ |
---|
859 | \includegraphics[page=1]{lst-issues-attach.pdf} |
---|
860 | \end{tabular} |
---|
861 | \end{lrbox} |
---|
862 | |
---|
863 | \begin{lrbox}{\myboxB} |
---|
864 | \begin{tabular}{@{}l@{}} |
---|
865 | \lstinput[language=C++]{20-35}{lst-issues-wrapped-byref.run.cpp} \\ |
---|
866 | \includegraphics[page=2]{lst-issues-attach.pdf} |
---|
867 | \end{tabular} |
---|
868 | \end{lrbox} |
---|
869 | |
---|
870 | \begin{lrbox}{\myboxC} |
---|
871 | \begin{tabular}{@{}l@{}} |
---|
872 | \lstinput[language=C++]{20-35}{lst-issues-wrapped-emplaced.run.cpp} \\ |
---|
873 | \includegraphics[page=3]{lst-issues-attach.pdf} |
---|
874 | \end{tabular} |
---|
875 | \end{lrbox} |
---|
876 | |
---|
877 | \subfloat[Intrusive]{\label{f:Intrusive}\usebox\myboxA} |
---|
878 | \hspace{6pt} |
---|
879 | \vrule |
---|
880 | \hspace{6pt} |
---|
881 | \subfloat[Wrapped reference]{\label{f:WrappedRef}\usebox\myboxB} |
---|
882 | \hspace{6pt} |
---|
883 | \vrule |
---|
884 | \hspace{6pt} |
---|
885 | \subfloat[Wrapped value]{\label{f:WrappedValue}\usebox\myboxC} |
---|
886 | |
---|
887 | \caption{ |
---|
888 | Three styles of link attachment: |
---|
889 | % \protect\subref*{f:Intrusive}~intrusive, \protect\subref*{f:WrappedRef}~wrapped reference, and \protect\subref*{f:WrappedValue}~wrapped value. |
---|
890 | The diagrams show the memory layouts that result after the code runs, eliding the head object \lstinline{reqs}; |
---|
891 | head objects are discussed in Section~\ref{toc:lst:issue:ident}. |
---|
892 | In \protect\subref*{f:Intrusive}, the field \lstinline{req.d} names a list direction; |
---|
893 | these are discussed in Section~\ref{toc:lst:issue:simultaneity}. |
---|
894 | In \protect\subref*{f:WrappedRef} and \protect\subref*{f:WrappedValue}, the type \lstinline{node} represents a |
---|
895 | library-internal type, which is \lstinline{std::_List_node} in the GNU implementation |
---|
896 | \see{\lstinline{/usr/include/c++/X/bits/stl_list.h}, where \lstinline{X} is the \lstinline{g++} version number}. |
---|
897 | } |
---|
898 | \label{fig:lst-issues-attach} |
---|
899 | \end{figure} |
---|
900 | |
---|
901 | Each diagrammed example is using the fewest dynamic allocations for its respective style: |
---|
902 | in intrusive, here is no dynamic allocation, in wrapped reference only the linked fields are dynamically allocated, and in wrapped value the copied data and linked fields are dynamically allocated. |
---|
903 | The advantage of intrusive is the control in memory layout and storage placement. |
---|
904 | Both wrapped styles have independent storage layout and imply library-induced heap allocations, with lifetime that matches the item's membership in the list. |
---|
905 | In all three cases, a @req@ object can enter and leave a list many times. |
---|
906 | However, in intrusive a @req@ can only be on one list at a time, unless there are separate link-fields for each simultaneous list. |
---|
907 | In wrapped reference, a @req@ can appear multiple times on the same or different lists simultaneously, but since @req@ is shared via the pointer, care must be taken if updating data also occurs simultaneously, \eg concurrency. |
---|
908 | In wrapped value, the @req@ is copied, which increases storage usage, but allows independent simultaneous changes; |
---|
909 | however, knowing which of the @req@ object is the ``true'' object becomes complex. |
---|
910 | \see*{\VRef{toc:lst:issue:simultaneity} for further discussion.} |
---|
911 | |
---|
912 | The implementation of @LIST_ENTRY@ uses a trick to find the links and the node containing the links. |
---|
913 | The macro @LIST_INSERT_HEAD(&reqs, &r2, d);@ takes the list header, a pointer to the node, and the offset of the link fields in the node. |
---|
914 | One of the fields generated by @LIST_ENTRY@ is a pointer to the node, which is set to the node address, \eg @r2@. |
---|
915 | Hence, the offset to the link fields provides an access to the entire node, \ie the node points at itself. |
---|
916 | For list traversal, @LIST_FOREACH(cur, &reqs_pri, by_pri)@, there is the node cursor, the list, and the offset of the link fields within the node. |
---|
917 | The traversal actually moves from link fields to link fields within a node and sets the node cursor from the pointer within the link fields back to the node. |
---|
918 | |
---|
919 | A further aspect of layout control is allowing the user to explicitly specify link fields controlling placement and attributes within the @req@ object. |
---|
920 | LQ allows this ability through the @LIST_ENTRY@ macro\footnote{It is possible to have multiple named linked fields allowing a node to appear on multiple lists simultaneously.}, which can be placed anywhere in the node. |
---|
921 | An example of an attribute on the link fields is cache alignment, possibly in conjunction with other @req@ fields, improving locality and/or avoiding false sharing. |
---|
922 | Supplying the link fields by inheritance makes them implicit and relies on compiler placement, such as the start or end of @req@, and no explicit attributes. |
---|
923 | Wrapped reference has no control over the link fields, but the separate data allows some control; |
---|
924 | wrapped value has no control over data or links. |
---|
925 | |
---|
926 | Another subtle advantage of intrusive arrangement is that a reference to a user-level item (@req@) is sufficient to navigate or manage the item's membership. |
---|
927 | In LQ, the intrusive @req@ pointer is the right argument type for operations @LIST_NEXT@ or @LIST_REMOVE@; |
---|
928 | there is no distinguishing a @req@ from ``a @req@ in a list.'' |
---|
929 | The same is not true of STL, wrapped reference or value. |
---|
930 | There, the analogous operations, @iterator::operator++()@, @iterator::operator*()@, and @list::erase(iterator)@, work on a parameter of type @list<T>::iterator@; |
---|
931 | there is no mapping from @req &@ to @list<req>::iterator@. %, for linear search. |
---|
932 | |
---|
933 | The advantage of wrapped is the abstraction of a data item from its list membership(s). |
---|
934 | In the wrapped style, the @req@ type can come from a library that serves many independent uses, |
---|
935 | which generally have no need for listing. |
---|
936 | Then, a novel use can put a @req@ in a list, without requiring any upstream change in the @req@ library. |
---|
937 | In intrusive, the ability to be listed must be planned during the definition of @req@. |
---|
938 | |
---|
939 | \begin{figure} |
---|
940 | \lstinput[language=C++]{100-117}{lst-issues-attach-reduction.hpp} |
---|
941 | \lstinput[language=C++]{150-150}{lst-issues-attach-reduction.hpp} |
---|
942 | \caption{ |
---|
943 | Simulation of wrapped using intrusive. |
---|
944 | Illustrated by pseudocode implementation of an STL-compatible API fragment using LQ as the underlying implementation. |
---|
945 | The gap that makes it pseudocode is that |
---|
946 | the LQ C macros do not expand to valid C++ when instantiated with template parameters---there is no \lstinline{struct El}. |
---|
947 | When using a custom-patched version of LQ to work around this issue, |
---|
948 | the programs of Figure~\ref{f:WrappedRef} and wrapped value work with this shim in place of real STL. |
---|
949 | Their executions lead to the same memory layouts. |
---|
950 | } |
---|
951 | \label{fig:lst-issues-attach-reduction} |
---|
952 | \end{figure} |
---|
953 | |
---|
954 | It is possible to simulate wrapped using intrusive, illustrated in Figure~\ref{fig:lst-issues-attach-reduction}. |
---|
955 | This shim layer performs the implicit dynamic allocations that pure intrusion avoids. |
---|
956 | But there is no reduction going the other way. |
---|
957 | No shimming can cancel the allocations to which wrapped membership commits. |
---|
958 | |
---|
959 | Because intrusion is a lower-level listing primitive, the system design choice is not between forcing users to use intrusion or wrapping. |
---|
960 | The choice is whether or not to provide access to an allocation-free layer of functionality. |
---|
961 | An intrusive-primitive library like LQ lets users choose when to make this tradeoff. |
---|
962 | A wrapped-primitive library like STL forces users to incur the costs of wrapping, whether or not they access its benefits. |
---|
963 | |
---|
964 | |
---|
965 | \subsection{Simultaneity: single vs.\ multi-static vs.\ dynamic} |
---|
966 | \label{toc:lst:issue:simultaneity} |
---|
967 | |
---|
968 | \begin{figure} |
---|
969 | \parbox[t]{3.5in} { |
---|
970 | \lstinput[language=C++]{20-60}{lst-issues-multi-static.run.c} |
---|
971 | }\parbox[t]{20in} { |
---|
972 | ~\\ |
---|
973 | \includegraphics[page=1]{lst-issues-direct.pdf} \\ |
---|
974 | ~\\ |
---|
975 | \hspace*{1.5in}\includegraphics[page=2]{lst-issues-direct.pdf} |
---|
976 | } |
---|
977 | \caption{ |
---|
978 | Example of simultaneity using LQ lists. |
---|
979 | The zoomed-out diagram (right/top) shows the complete multi-linked data structure. |
---|
980 | This structure can navigate all requests in priority order ({\color{blue}blue}), and navigate among requests with a common request value ({\color{orange}orange}). |
---|
981 | The zoomed-in diagram (right/bottom) shows how the link fields connect the nodes on different lists. |
---|
982 | } |
---|
983 | \label{fig:lst-issues-multi-static} |
---|
984 | \end{figure} |
---|
985 | |
---|
986 | \newterm{Simultaneity} deals with the question: |
---|
987 | In how many different lists can a node be stored, at the same time? |
---|
988 | Figure~\ref{fig:lst-issues-multi-static} shows an example that can traverse all requests in priority order (field @pri@) or navigate among requests with the same request value (field @rqr@). |
---|
989 | Each of ``by priority'' and ``by common request value'' is a separate list. |
---|
990 | For example, there is a single priority-list linked in order [1, 2, 2, 3, 3, 4], where nodes may have the same priority, and there are three common request-value lists combining requests with the same values: [42, 42], [17, 17, 17], and [99], giving four head nodes one for each list. |
---|
991 | The example shows a list can encompass all the nodes (by-priority) or only a subset of the nodes (three request-value lists). |
---|
992 | |
---|
993 | As stated, the limitation of intrusive is knowing apriori how many groups of links are needed for the maximum number of simultaneous lists. |
---|
994 | Thus, the intrusive LQ example supports multiple, but statically many, link lists. |
---|
995 | Note, it is possible to reuse links for different purposes, \eg if a list in linked one way at one time and another way at another time, and these times do not overlap, the two different linkings can use the same link fields. |
---|
996 | This feature is used in the \CFA runtime, where a thread node may be on a blocked or running list, but never on both simultaneously. |
---|
997 | |
---|
998 | Now consider the STL in the wrapped-reference arrangement of Figure~\ref{f:WrappedRef}. |
---|
999 | Here it is possible to construct the same simultaneity by creating multiple STL lists, each pointing at the appropriate nodes. |
---|
1000 | Each group of intrusive links become the links for each separate STL list. |
---|
1001 | The upside is the unlimited number of lists a node can be associated with simultaneously, as any number of STL lists can be created dynamically. |
---|
1002 | The downside is the dynamic allocation of the link nodes and managing multiple lists. |
---|
1003 | Note, it might be possible to wrap the multiple lists in another type to hide this implementation issue. |
---|
1004 | |
---|
1005 | Now consider the STL in the wrapped-value arrangement of Figure~\ref{f:WrappedValue}. |
---|
1006 | Again, it is possible to construct the same simultaneity by creating multiple STL lists, each copying the appropriate nodes, where the intrusive links become the links for each separate STL list. |
---|
1007 | The upside is the same as for wrapped-reference arrangement with an unlimited number of list bindings. |
---|
1008 | The downside is the dynamic allocation, significant storage usage, and cost of copying nodes. |
---|
1009 | As well, it is unclear how node updates work in this scenario, without some notation of ultimately merging node information. |
---|
1010 | |
---|
1011 | % https://www.geeksforgeeks.org/introduction-to-multi-linked-list/ -- example of building a bespoke multi-linked list out of STL primitives (providing indication that STL doesn't offer one); offers dynamic directionality by embedding `vector<struct node*> pointers;` |
---|
1012 | |
---|
1013 | % When allowing multiple static directions, frameworks differ in their ergonomics for |
---|
1014 | % the typical case: when the user needs only one direction, vs.\ the atypical case, when the user needs several. |
---|
1015 | % LQ's ergonomics are well-suited to the uncommon case of multiple list directions. |
---|
1016 | % Its intrusion declaration and insertion operation both use a mandatory explicit parameter naming the direction. |
---|
1017 | % This decision works well in Figure~\ref{fig:lst-issues-multi-static}, where the names @by_pri@ and @by_rqr@ work well, |
---|
1018 | % but it clutters Figure~\ref{f:Intrusive}, where a contrived name must be invented and used. |
---|
1019 | % The example uses @x@; @reqs@ would be a more readily ignored choice. \PAB{wording?} |
---|
1020 | |
---|
1021 | An alternative system providing intrusive data-structures is \uCpp, a concurrent extension of \CC. |
---|
1022 | It provides a basic set of intrusive lists~\cite[appx.~F]{uC++}, where the link fields are defined with the data fields using inheritance. |
---|
1023 | \begin{cquote} |
---|
1024 | \setlength{\tabcolsep}{15pt} |
---|
1025 | \begin{tabular}{@{}ll@{}} |
---|
1026 | \multicolumn{1}{c}{singly-linked list} & \multicolumn{1}{c}{doubly-linked list} \\ |
---|
1027 | \begin{c++} |
---|
1028 | struct Node : public uColable { |
---|
1029 | int i; // data |
---|
1030 | Node( int i ) : i{ i } {} |
---|
1031 | }; |
---|
1032 | \end{c++} |
---|
1033 | & |
---|
1034 | \begin{c++} |
---|
1035 | struct Node : public uSeqable { |
---|
1036 | int i; // data |
---|
1037 | Node( int i ) : i{ i } {} |
---|
1038 | }; |
---|
1039 | \end{c++} |
---|
1040 | \end{tabular} |
---|
1041 | \end{cquote} |
---|
1042 | A node can be placed in the following data structures depending on its link fields: @uStack@ and @uQueue@ (singly linked), and @uSequence@ (doubly linked). |
---|
1043 | A node inheriting from @uSeqable@ can appear in a singly or doubly linked structure. |
---|
1044 | Structure operations implicitly know the link-field location through the inheritance. |
---|
1045 | \begin{c++} |
---|
1046 | uStack<Node> stack; |
---|
1047 | Node node; |
---|
1048 | stack.push( node ); // link fields at beginning of node |
---|
1049 | \end{c++} |
---|
1050 | |
---|
1051 | Simultaneity cannot be done with multiple inheritance, because there is no mechanism to either know the order of inheritance fields or name each inheritance. |
---|
1052 | Instead, a special type is require that contains the link fields and points at the node. |
---|
1053 | \begin{cquote} |
---|
1054 | \setlength{\tabcolsep}{10pt} |
---|
1055 | \begin{tabular}{@{}ll@{}} |
---|
1056 | \begin{c++} |
---|
1057 | struct NodeDL : public uSeqable { |
---|
1058 | @Node & node;@ // node pointer |
---|
1059 | NodeDL( Node & node ) : node( node ) {} |
---|
1060 | Node & get() const { return node; } |
---|
1061 | }; |
---|
1062 | \end{c++} |
---|
1063 | & |
---|
1064 | \begin{c++} |
---|
1065 | struct Node : public uColable { |
---|
1066 | int i; // data |
---|
1067 | @NodeDL nodeseq;@ // embedded intrusive links |
---|
1068 | Node( int i ) : i{ i }, @nodeseq{ this }@ {} |
---|
1069 | }; |
---|
1070 | \end{c++} |
---|
1071 | \end{tabular} |
---|
1072 | \end{cquote} |
---|
1073 | This node can now be inserted into a doubly-linked list through the embedded intrusive links. |
---|
1074 | \begin{c++} |
---|
1075 | uSequence<NodeDL> sequence; |
---|
1076 | sequence.add_front( @node.nodeseq@ ); $\C{// link fields in embedded type}$ |
---|
1077 | NodeDL nodedl = sequence.remove( @node.nodeseq@ ); |
---|
1078 | int i = nodedl.@get()@.i; $\C{// indirection to node}$ |
---|
1079 | \end{c++} |
---|
1080 | Hence, the \uCpp approach optimizes one set of intrusive links through the \CC inheritance mechanism, and falls back onto the LQ approach of explicit declarations for additional intrusive links. |
---|
1081 | However, \uCpp cannot apply the LQ trick for finding the links and node. |
---|
1082 | |
---|
1083 | The major ergonomic difference among the approaches is naming and name usage. |
---|
1084 | The intrusive model requires naming each set of intrusive links, \eg @by_pri@ and @by_rqr@ in \VRef[Figure]{fig:lst-issues-multi-static}. |
---|
1085 | \uCpp cheats by using inheritance for the first intrusive links, after which explicit naming of intrusive links is required. |
---|
1086 | Furthermore, these link names must be used in all list operations, including iterating, whereas wrapped reference and value hide the list names in the implicit dynamically-allocated node-structure. |
---|
1087 | At issue is whether an API for simultaneity can support one \emph{implicit} list, when several are not wanted. |
---|
1088 | \uCpp allows it, LQ does not, and the STL does not have this question. |
---|
1089 | |
---|
1090 | |
---|
1091 | \subsection{User integration: preprocessed vs.\ type-system mediated} |
---|
1092 | |
---|
1093 | \PAB{What do you want to say here?} |
---|
1094 | |
---|
1095 | % example of poor error message due to LQ's preprocessed integration |
---|
1096 | % programs/lst-issues-multi-static.run.c:46:1: error: expected identifier or '(' before 'do' |
---|
1097 | % 46 | LIST_INSERT_HEAD(&reqs_rtr_42, &r42b, by_rqr); |
---|
1098 | % | ^~~~~~~~~~~~~~~~ |
---|
1099 | % |
---|
1100 | % ... not a wonderful example; it was a missing semicolon on the preceding line; but at least real |
---|
1101 | |
---|
1102 | |
---|
1103 | \subsection{List identity: headed vs.\ ad-hoc} |
---|
1104 | \label{toc:lst:issue:ident} |
---|
1105 | |
---|
1106 | All examples so far use distinct user-facing types: |
---|
1107 | an item found in a list (type @req@ of variable @r1@, see \VRef[Figure]{fig:lst-issues-attach}), and a list (type @reql@ of variable @reqs_pri@, see \VRef[Figure]{fig:lst-issues-ident}). |
---|
1108 | The latter type is a head. |
---|
1109 | The resulting identity model (empty list) is just the head. |
---|
1110 | A bespoke ``pointer to next @req@'' implementation often omits the header; |
---|
1111 | hence, a pointer to any node can traverse its link fields: right or left and around, depending on the data structure. |
---|
1112 | The resulting identity model is ad-hoc. |
---|
1113 | |
---|
1114 | Figure~\ref{fig:lst-issues-ident} shows the identity model's impact on |
---|
1115 | the existence of certain conceptual constructs, like zero-lengths lists and unlisted elements. |
---|
1116 | In headed thinking, there are length-zero lists (heads with no elements), and an element can be listed or not listed. |
---|
1117 | In ad-hoc thinking, there are no length-zero lists and every element belongs to a list of length at least one. |
---|
1118 | By omitting the head, elements can enter into an adjacency relationship, without requiring allocation for a head for the list, or finding a correct existing head. |
---|
1119 | |
---|
1120 | \begin{figure} |
---|
1121 | \centering |
---|
1122 | \includegraphics{lst-issues-ident.pdf} |
---|
1123 | \caption{ |
---|
1124 | Comparison of headed and ad-hoc list identities, for various list lengths. |
---|
1125 | Pointers are logical, meaning that no claim is intended about which part of an object is being targeted. |
---|
1126 | } |
---|
1127 | \label{fig:lst-issues-ident} |
---|
1128 | \end{figure} |
---|
1129 | |
---|
1130 | A head defines one or more element roles, among elements that share a transitive adjacency. |
---|
1131 | ``First'' and ``last'' are element roles. |
---|
1132 | One moment's ``first'' need not be the next moment's. |
---|
1133 | |
---|
1134 | There is a cost to maintaining these roles. |
---|
1135 | A runtime component of this cost is evident in LQ's offering the choice of type generators @LIST@ \vs @TAILQ@. |
---|
1136 | Its @LIST@ maintains a ``first,'' but not a ``last;'' its @TAILQ@ maintains both roles. |
---|
1137 | (Both types are doubly linked and an analogous choice is available for singly linked.) |
---|
1138 | |
---|
1139 | TODO: finish making this point |
---|
1140 | |
---|
1141 | See WIP in lst-issues-adhoc-*.ignore.*. |
---|
1142 | |
---|
1143 | The code-complexity component of the cost ... |
---|
1144 | |
---|
1145 | Ability to offer heads is good. Point: Does maintaining a head mean that the user has to provide more state when manipulating the list? Requiring the user to do so is bad, because the user may have lots of "list" typed variables in scope, and detecting that the user passed the wrong one requires testing all the listing edge cases. |
---|
1146 | |
---|
1147 | |
---|
1148 | \subsection{End treatment: cased vs.\ uniform } |
---|
1149 | |
---|
1150 | A linear (non-circular), nonempty linked-list has a first element and a last element, whether or not the list is headed. |
---|
1151 | A first element has no predecessor and a last element has no successor. |
---|
1152 | |
---|
1153 | \begin{figure} |
---|
1154 | \centering |
---|
1155 | \includegraphics{lst-issues-end.pdf} |
---|
1156 | \caption{ |
---|
1157 | LQ sub-object-level representation of links and ends. |
---|
1158 | Each object's memory is pictured as a vertical strip. |
---|
1159 | Pointers' target locations, within these strips, are significant. |
---|
1160 | Uniform treatment of the first-end is evident from an assertion like \lstinline{(**this.pred == this)} holding for all nodes \lstinline{this}, including the first one. |
---|
1161 | Cased treatment of the last-end is evident from the symmetric proposition, \lstinline{(this.succ.pred == &this.succ)}, failing when \lstinline{this} is the last node. |
---|
1162 | } |
---|
1163 | \label{fig:lst-issues-end} |
---|
1164 | \end{figure} |
---|
1165 | |
---|
1166 | End treatment refers to how the list represents the lack of a predecessor/successor. |
---|
1167 | The following elaboration refers to the LQ representations, detailed in Figure~\ref{fig:lst-issues-end}. |
---|
1168 | The most obvious representation, a null pointer, mandates a cased end treatment. |
---|
1169 | LQ uses this representation for its successor/last. |
---|
1170 | Consider the operation of inserting after a given element. |
---|
1171 | A doubly-linked list must update the given node's successor, to make its predecessor-pointer refer to the new node. |
---|
1172 | This step must happen when the given node has a successor (when its successor pointer is non-null), |
---|
1173 | and must be skipped when it does not (when its successor pointer cannot be navigated). |
---|
1174 | So this operation contains a branch, to decide which case is happening. |
---|
1175 | All branches have pathological cases where branch prediction's success rate is low and the execution pipeline is stalling often. |
---|
1176 | Hence, this issue is implementation-level, relevant to achieving high performance. |
---|
1177 | |
---|
1178 | This branch is sometimes avoidable; the result is a uniform end treatment. |
---|
1179 | Here is one example of such an implementation that works for a headed list. |
---|
1180 | LQ uses this representation for its predecessor/first. (All LQ offerings are headed at the front.) |
---|
1181 | For predecessor/first navigation, the relevant operation is inserting before a given element. |
---|
1182 | LQ's predecessor representation is not a pointer to a node, but a pointer to a pseudo-successor pointer. |
---|
1183 | When there is a predecessor node, that node contains a real-successor pointer; it is the target of the reference node's predecessor pointer. |
---|
1184 | When there is no predecessor node, the reference node (now known to be first node) acts as the pseudo-successor of the list head. |
---|
1185 | The list head contains a pointer to the first node. |
---|
1186 | When inserting before the first node, the list head's first-pointer is the one that must change. |
---|
1187 | So, the first node's ``predecessor'' pointer (to a pseudo-successor pointer) is set as the list head's first-pointer. |
---|
1188 | Now, inserting before a given element does the same logic in both cases: |
---|
1189 | follow the guaranteed-non-null predecessor pointer, and update what you find there to refer to the new node. |
---|
1190 | Applying this trick makes it possible to have list management routines that are completely free of conditional control-flow. |
---|
1191 | Considering a path length of only a few instructions (less than the processor's pipeline length), |
---|
1192 | such list management operations are often practically free, |
---|
1193 | with all the variance being due to the (inevitable) cache status of the nodes being managed. |
---|
1194 | |
---|
1195 | \section{String} |
---|
1196 | |
---|
1197 | A string is a sequence of symbols, where the form of a symbol can vary significantly: 7/8-bit characters (ASCII/Latin-1), or 2/4/8-byte (UNICODE) characters/symbols or variable length (UTF-8/16/32) characters. |
---|
1198 | A string can be read left-to-right, right-to-left, top-to-bottom, and have stacked elements (Arabic). |
---|
1199 | |
---|
1200 | A C character constant is an ASCII/Latin-1 character enclosed in single-quotes, \eg @'x'@, @'@\textsterling@'@. |
---|
1201 | A wide C character constant is the same, except prefixed by the letter @L@, @u@, or @U@, \eg @u'\u25A0'@ (black square), where the @\u@ identifies a universal character name. |
---|
1202 | A character can be formed from an escape sequence, which expresses a non-typable character @'\f'@ form feed, a delimiter character @'\''@ embedded single quote, or a raw character @'\xa3'@ \textsterling. |
---|
1203 | |
---|
1204 | A C character string is zero or more regular, wide, or escape characters enclosed in double-quotes @"xyz\n"@. |
---|
1205 | The kind of characters in the string is denoted by a prefix: UTF-8 characters are prefixed by @u8@, wide characters are prefixed by @L@, @u@, or @U@. |
---|
1206 | |
---|
1207 | For UTF-8 string literals, the array elements have type @char@ and are initialized with the characters of the multi-byte character sequences, \eg @u8"\xe1\x90\x87"@ (Canadian syllabics Y-Cree OO). |
---|
1208 | For wide string literals prefixed by the letter @L@, the array elements have type @wchar_t@ and are initialized with the wide characters corresponding to the multi-byte character sequence, \eg @L"abc@$\mu$@"@ and are read/printed using @wsanf@/@wprintf@. |
---|
1209 | The value of a wide-character is implementation-defined, usually a UTF-16 character. |
---|
1210 | For wide string literals prefixed by the letter @u@ or @U@, the array elements have type @char16_t@ or @char32_t@, respectively, and are initialized with wide characters corresponding to the multi-byte character sequence, \eg @u"abc@$\mu$@"@, @U"abc@$\mu$@"@. |
---|
1211 | The value of a @"u"@ character is an UTF-16 character; |
---|
1212 | the value of a @"U"@ character is an UTF-32 character. |
---|
1213 | The value of a string literal containing a multi-byte character or escape sequence not represented in the execution character set is implementation-defined. |
---|
1214 | |
---|
1215 | C strings are null-terminated rather than maintaining a separate string length. |
---|
1216 | \begin{quote} |
---|
1217 | Technically, a string is an array whose elements are single characters. |
---|
1218 | The compiler automatically places the null character @\0@ at the end of each such string, so programs can conveniently find the end. |
---|
1219 | This representation means that there is no real limit to how long a string can be, but programs have to scan one completely to determine its length.~\cite[p.~36]{C:old} |
---|
1220 | \end{quote} |
---|
1221 | Unfortunately, this design decision is both unsafe and inefficient. |
---|
1222 | It is common error in C to forget the storage in a character array for the terminator or overwrite the terminator, resulting in array overruns in string operations. |
---|
1223 | The need to repeatedly scan an entire string to determine its length can result in significant cost, as it is impossible to cache the length in many cases. |
---|
1224 | |
---|
1225 | C strings are fixed size because arrays are used for the implementation. |
---|
1226 | However, string manipulation commonly results in dynamically-sized temporary and final string values, \eg @strcpy@, @strcat@, @strcmp@, @strlen@, @strstr@, \etc. |
---|
1227 | As a result, storage management for C strings is a nightmare, quickly resulting in array overruns and incorrect results. |
---|
1228 | |
---|
1229 | Collectively, these design decisions make working with strings in C, awkward, time consuming, and unsafe. |
---|
1230 | While there are companion string routines that take the maximum lengths of strings to prevent array overruns, \eg @strncpy@, @strncat@, @strncpy@, that means the semantics of the operation can fail because strings are truncated. |
---|
1231 | Suffice it to say, C is not a go-to language for string applications, which is why \CC introduced the dynamically-sized @string@ type. |
---|