5 ms·
C syntax is overall simple. The only syntax often confuses me is function typedef.
by tapirl 5y ago
C syntax is overall simple. The only syntax often confuses me is function typedef.
- jimbob45 5y agoAnd ostensibly function pointers/function pointer calling.
- benburleson 5y agoOne of the best parts!
- Koshkin 5y agoWhy? Essentially, a typedef only differs from a variable declaration by, well, having the word ‘typedef’ in front of it, functions or function pointers being no exception.
- tapirl 5y agoSorry, I should be more accurately. It is function type uses. In fact, I'm not confused with simple function types. It is just that sometimes, complex function types are convolutions for me.
- yakubin 5y agoC syntax isn't simple. Unlike most modern languages, which have a context-free grammar, it has a context-sensitive grammar. You can see it in the following example: #define a stdio #define header <a.h> // five tokens: <, a, ., h, > (otherwise preprocessor wouldn't substitue "a") #include header // one token after substitution: <stdio.h> int main() { struct { int h; } stdio; a.h = 0; printf("%d\n", stdio.h); } What happened here is: 1. In #include directives a thing like <stdio.h> is one token; everywhere else (including other preprocessor directives) it is five tokens; 2. But an #include directive can have multiple tokens on the right hand side of "#include" before macro substitution. After macro substitution the strings are lexed again - into ONE token. 3. In main() "a.h" is substituted with "stdio.h" - this time it's THREE tokens. (Oh, and writing "#include <a.h>" will produce an error, because here we have a single token <a.h>, so no "a" to be substituted, and normally there is no "a.h" header file in include path.) So the same string gets lexed into a different number of tokens in different places; and that happens even after it got lexed first into the same number of tokens before macro substitution. Now have an example of a C file that compiles with GCC, but not with Clang: #define CMPS x /* */ <y> #define ALMOST_SHIFT<CMPS #define FWD_FST(x, y) x #define CONCAT(x, y) x ## y #define x # /* LOL */ define y stdio.h #include CMPS #undef x #undef y #include FWD_FST(<stdint.h>, y) // btw. comma is a valid character in a header name. but this will work anyway, // because lexing in C works in mysterious ways #include CONCAT(<std,def.h>) int main() { int x = 0; int y = 0; int z = 0; printf("%d\n", CMPS z); // printf("%d\n", z<ALMOST_SHIFT); // doesn't compile // but the rest works fine... } So clearly the C syntax isn't simple. It's so not simple, that compiler writers can't agree on what exactly it is. (I've been writing such programs for the purpose of using them as edgecases to test my C compiler.) Then of course you have the type declaration fiasco: <https://blog.golang.org/declaration-syntax https://blog.golang.org/declaration-syntax> Then there if of course the awful ternary operator. Oh, and I almost forgot: spaces are insignificant... unless you're in a "#define" directive. Talk about language consistency... Those things not only make life hard for compiler writers, but also for language users, because, I don't know about you, but when I write a program the smallest unit that I use to think about its source code is the token. When I can't easily predict tokenization, then something went really wrong. Same with other syntax complications. Guy Steele once said that when he designs a language he verifies that its grammar is LL(1) with a parser generator, which helps make sure that it's not only easy to write the parser and that parsing can be very efficient, but also that it's easy to understand by humans. C fails here big time.
- zabzonk 5y agoYou seem to be talking about the preprocessor, not C itself, and I'm not sure that anything you say about it is true (the preprocessor is complicated). Certainly, your first example won't compile without warnings for any sensible GCC options.
- tom_ 5y agoA better example of C's context-sensitive grammar might be the expression "(T)*x", which parses differently depending on whether T names a type or a value.
- yakubin 5y agoThat's a good example, but of a different thing. My examples show that the input is lexed differently based on what part of AST is being produced at a given point. Your example shows that input is parsed into different ASTs based on information from the type system. I.e my examples show that in C the lexer can't be separated from the parser, and your example shows that the parser can't be separated from the type system.
- yakubin 5y ago> You seem to be talking about the preprocessor, not C itself, and I'm not sure that anything you say about it is true (the preprocessor is complicated). The preprocessor is part of the language. Both in literal sense and in real-world. All C programs use the preprocessor. You need to deal with it. Also, the grammar of the preprocessor is intertwined with C proper due to "#if" directives. The expression on the right hand side of an "#if" is parsed according to the grammar of C proper. > I'm not sure that anything you say about it is true (the preprocessor is complicated). You can run the examples under compilers and verify. Also, the part about it being context-sensitive (with regard to tokenization in #include directives vs everywhere else) is explicitly noted in the standard. Nothing controversial here. > Certainly, your first example won't compile without warnings for any sensible GCC options. Compiles just fine without any warnings with GCC with options -Wall -Wextra.