Для того, кому нужно сделать транслятор языка Си, нужно понимать не как на нём писать, а как лучше сделать его транслятор. Планируемый транслятор должен понимать реальные исходники, такие как libmysqlclient, libpq, а в них может встретиться всякое. Полезно изучать, на чём споткнулись другие.
В этом помогает CIL, C Intermediate Language, на который предусмотрительно сохранена документация. Там пишут:
When I (George) started to write CIL I thought it was going to take two weeks. Exactly a year has passed since then and I am still fixing bugs in it. This gross underestimate was due to the fact that I thought parsing and making sense of C is simple. You probably think the same. What I did not expect was how many dark corners this language has, especially if you want to parse real-world programs such as those written for GCC or if you are more ambitious and you want to parse the Linux or Windows NT sources (both of these were written without any respect for the standard and with the expectation that compilers will be changed to accommodate the program).
The following examples were actually encountered either in real programs or are taken from the ISO C99 standard or from the GCC’s testcases. My first reaction when I saw these was: Is this C?. The second one was : What the hell does it mean?.
If you are contemplating doing program analysis for C on abstract-syntax trees then your analysis ought to be able to handle these things. Or, you can use CIL and let CIL translate them into clean C code.
Why does the following code return 0 and not -1? (Answer: because sizeof is unsigned, thus the result of the subtraction is unsigned, thus the shift is logical.)
Functions and function pointers are implicitly converted to each other.
int (*pf)(void);
int f(void) {
pf = &f; // This looks ok
pf = ***f; // Dereference a function?
pf(); // Invoke a function pointer?
(****pf)(); // Looks strange but Ok
(***************f)(); // Also Ok
}
Initializer with designators are one of the hardest parts about ISO C. Neither MSVC or GCC implement them fully. GCC comes close though. What is the final value of i.nested.y and i.nested.z? (Answer: 2 and respectively 6).
struct {
int x;
struct {
int y, z;
} nested;
} i = { .nested.y = 5, 6, .x = 1, 2 };
Another one with constructed literals. This one is legal according to the GCC documentation but somehow GCC chokes on (it works in CIL though). This code returns 2.
In the example below there is one copy of “bar” and two copies of “pbar” (static prototypes at block scope have file scope, while for all other types they have block scope).
Two years after heavy use of CIL, by us and others, I discovered a bug in the parser. The return value of the following function depends on what precedence you give to casts and unary minus:
The answer depends on whether the optimizations are turned on. If they are then the answer is 3 (the first definition is inlined at all occurrences until the second definition). If the optimizations are off, then the first definition is ignore (treated like a prototype) and the answer is 4.
CIL will misbehave on this example, if the optimizations are turned off (it always returns 3).
GCC allows you to cast an object of a type T into a union as long as the union has a field of that type:
union u {
int i;
struct s {
int i1, i2;
} s;
};
union u x = (union u)6;
int main() {
struct s y = {1, 2};
union u z = (union u)y;
}
The “alias” attribute on a function declaration tells the linker to treat this declaration as another name for the specified function. CIL will replace the declaration with a trampoline function pointing to the specified target.
static int bar(int x, char y) {
return x + y;
}
//foo is considered another name for bar.
int foo(int x, char y) __attribute__((alias("bar")));
This compiler has few extensions, so there is not much to say here.
Why does the following code return 0 and not -1? (Answer: because of a bug in Microsoft Visual C. It thinks that the shift is unsigned just because the second operator is unsigned. CIL reproduces this bug when in MSVC mode.)
return -3 >> (8 * sizeof(int));
Unnamed fields in a structure seem really strange at first. It seems that Microsoft Visual C introduced this extension, then GCC picked it up (but in the process implemented it wrongly: in GCC the field y overlaps with x!).
struct {
int x;
struct {
int y, z;
struct {
int u, v;
};
};
} a;
return a.x + a.y + a.z + a.u + a.v;