Live data from Hacker News

Some Obscure C Features

multun.net

131–140 of 147 posts

Re: Some Obscure C Features

#131
> VLA typedef ... I have no clue how this could ever be useful.

I used this feature recently. I had several arrays of the same size and type, and the size was determined at runtime. The VLA typedef let me avoid duplicate type signatures which I find more readable.

    int N = atoi(argv[1]);
    typedef int grid[N][N];
    grid board;
    grid best;
    grid cache;

Re: Some Obscure C Features

#133
post #9

Here is an obscure c feature: int main() { int a = 8; { int a = 4; /* a is only scoped to this block */ } printf("%d", a); /* prints 8 */ } It is also why C++ is not a strict superset of C

> It is also why C++ is not a strict superset of C Can you explain? That code in C++ also scopes ‘a’ to the block. EDIT: I see you’ve edited the code, but I think it’s still true in C++. I’ve often done that for RAII and unless I’m mistaken it works just as well when shadowing variables like you’re doing as when not.

Agreed. And I have also used it in C++ for RAII purposes. In C++, braces introduce a scope, and objects local to that scope will be destructed upon exit.

Re: Some Obscure C Features

#134

Earlier quoted context omitted.

Another neat note IIRC is that array parameter sizes don't actually do anything, they just are there for semantic purposes and get treated as raw pointers. So if you do void func(int x[10]); You're free to call it like int k[5]; func(k); And you won't get any warnings. Unsettling!

The wording in the C FAQ is that arrays “decay” into pointers when you pass them to functions. Which they explain as the reason why you can’t know the size of a passed array (at least in standard C.) The C FAQ is pretty old though, I’ve always wondered how much of that advice changed in C99/C11... from cursory googling things don’t seem to have changed much.

It's funny that K&R chose to have arrays "decay" to pointers, but to allow structs to be passed by value. Thus you can actually pass arrays by value if and only if you wrap them in a struct:

  struct foo { char a[5]; };
  
  void f(struct foo x) { x.a[4] = '\0'; printf("%s", x.a); }
  
  int main(void) {
    struct foo x;
    
    memcpy(x.a, "too big", sizeof(x.a));
    
    f(x)
    
    printf("%s", x.a); /* read past end of x, crash */
    
    return 0;
  }
I'm thankful they didn't make structs decay into pointers!

Re: Some Obscure C Features

#135
post #49
post #34

A nice but I guess more of a linker feature is if you declare a function as __weak__, you can check it at runtime for == NULL to determine if the application was built with the function defined.

I would say most interesting linker features are non standard, so outside of the scope of this article :/

True, however, it is precisely the stupid linker tricks that make C such an interesting and powerful language nowadays. Weak symbols. Interposition (LD_PRELOAD). dlopen() and friends. Filters. Direct binding / versioned symbols. ELF semantics in general (which make the use of one flat symbol namespace safer).

Re: Some Obscure C Features

#136
This was c++ and not C, but it is a preprocessor pitfall.

I needed to compare and older and newer version of some file from the RCS, so I saved temporary copies named "new" and "old". diff told me what I needed to know, but I failed to delete those temp files.

Hours later I typed "make" to build my program and got all sorts of errors deeply nested in some library function. Did someone misconfigure the server I was on? OK, maybe it is an incremental build problem? etc. It took took long to figure out the problem.

It turns out that during compilation, as one of the library .h files was being scanned, it contained #include , which picked up the junk file in my working directory instead of using the C library.

Re: Some Obscure C Features

#137
post #97

No mention of trigraphs? They are one of my favourite obscure C language features that I've never used. Excerpt from GCC man page: Trigraph: ??( ??) ?? ??= ??/ ??' ??! ??- Replacement: [ ] { } # \ ^ | ~ Missing backslash on your keyboard? No problem, just type ??/ instead.

After long debates (since IBM needs them in EBCDIC machines, like z/Series) they were dropped from C++ (I believe C++17, but might already be C++14) so if you use them in a header this might cause incompatibilities.

Re: Some Obscure C Features

#138
post #126

A more obscure feature is the uncommon usage of comma operator. We often use the comma operator in variable declaration and in for loops. But it can also be used in any expression. For instance, the next line has a valid C construct: return a, b, c; This is particularly useful for setting variable when retuning after an error. if (ret = io(x)) return errno = 10, -1; The possibilities are endless. Another example: if…

Also, the comma operator forces a left to right evaluation order. Surprisingly, you can override it in C++. I haven't seen anyone do it, but you can. If you find a good, productive override for the comma operator, please post about it.

So that's why it return the value of last expression!

Re: Some Obscure C Features

#139
post #97

No mention of trigraphs? They are one of my favourite obscure C language features that I've never used. Excerpt from GCC man page: Trigraph: ??( ??) ?? ??= ??/ ??' ??! ??- Replacement: [ ] { } # \ ^ | ~ Missing backslash on your keyboard? No problem, just type ??/ instead.

Related to trigraphs are the alternative logical operator keywords like `and` and `or`. I'm surprised people don't use them more often because they're nicer to read than && and ||. In C, you must #include but I think they're standard keywords.

C++ code example on Godbolt: https://godbolt.org/z/ED6tXK

https://en.cppreference.com/w/cpp/language/operator_alternat...

Re: Some Obscure C Features

#140

Earlier quoted context omitted.

cdecl> explain const void * const_pointer declare const_pointer as pointer to const void cdecl> explain void * const const_value declare const_value as const pointer to void cdecl> The first must be the one that segfaults on write, IFF the compiler chooses to place it in the .text (as it should).

My (admittedly naive) understanding of the ordeal leads me to believe that it is that the 1st would not segfault but the second will since declaring it as a const pointer will create additional memory constraints. Testing it on my machine with the following code seems to validate this hypothesis. //file: test.c #include const void * const_pointer = &const_pointer; void * const const_value = &const_value; int main() {…

In the first version, const_pointer is a non-const variable (so located in .data) holding a pointer to potentially constant data—you can't modify the data through that pointer without a typecast, but the actual location in memory may be mutable. That's why you don't get a segfault when you cast away the const and modify the data—the destination (const_pointer) is not const even though the const-qualified pointer would allow it to be.

In the second case the const_value variable itself is const-qualified and thus located in .rodata, but the pointer itself is not const-qualified so nothing prevents you from attempting to modify the data through that pointer. This is why you get a compiler warning about discarding the 'const' qualifier in the initialization. Since const_value is in .rodata, writing to it through the pointer causes a segfault.

As Sean1708 pointed out, it's more obvious what is going on if you place the 'const' qualifier immediately before the thing it's modifying, which is either the pointer operator or the variable name, never the type itself:

    void const *const_pointer = &const_pointer;
    void *const const_value   = &const_value;
What would something like "const int" even mean on its own, anyway? There is no such thing as a mutable integer. It's the memory location holding the integer which may be either mutable or immutable.
Post reply on HN