Live data from Hacker News

Some Obscure C Features

multun.net

121–130 of 147 posts

Re: Some Obscure C Features

#121
post #76

Earlier quoted context omitted.

> If the operand is the name of an object [...] what does it mean to evaluate `n`? When you say "n", syntactically, you have a primary expression that is an identifier. So you follow the rules for evaluating an identifier, which will produce the value. C doesn't describe it very well, but the value of the expression is the value of the object. In terms of how it is implemented in actual compilers, this would mean iss…

Sorry, in the first example I meant to write (adding a declaration and initialization for n): int n = 42; int vla[n]; sizeof vla; not `sizeof n`). (It doesn't look like I can edit a comment.) Logically, evaluating the expression `vla` would mean reading the contents of the array object, which means reading the value of each of its elements. But there's clearly no need to do that to determine its size -- and if you ac…

Your example is unnecessarily tame. The value of n need not be known as compile time, e.g. it may be input by the user at runtime.

Re: Some Obscure C Features

#122

The article has examples of array parameters like int b[const 42][24][*] but you can get more fun with variably modified array parameters instead of just constants like 42. For example, double sum_a_weird_shaped_matrix(int n, double array[n][3*n]) { double total = 0; for (int x = 0; x has a variable and a more complicated expression in those positions. But those variably modified parameters can have arbitrary express…

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.

Re: Some Obscure C Features

#123
post #117

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…

The comma character as a token in variable is decalararions is not the same thing as the comma operator.

You are correct. My mistake.

Re: Some Obscure C Features

#124

Most of these are due to the cruft added in C99 and later. Compile-time trees are possible without compound literals. More than twenty years ago, I made a hyper-linked help screen system a GUI app whose content was all statically declared C structures with pointers to each other. At file scope, you can make circular structures, thanks to tentative definitions, which can forward-declare the existence of a name, whose…

I haven't heard of "tentative definitions" before. Couldn't you just replace it with a regular declaration i.e. extern foo n1, n2; Is there any benefit of tentative definitions over this?

Yes; the "extern" is potentially confusing when the definition is located in the same file below. They usually inform the reader of the code to look in some other file for the definition, and usually appear in header files.

Re: Some Obscure C Features

#125
post #81

I was reading the source code for a NES assembler written in pre-C99 C, and there was an odd C feature used in it that I haven't really seen anywhere else. It was before C had built-in booleans and the author had defined their own, but true was: void * true_ptr = &true_ptr; true_ptr is a pointer to itself. So however many times you deference it: printf("%p\n", true_ptr); printf("%p\n", &true_ptr); printf("%p\n", *((v…

  const void * const_pointer = &const_pointer;
  void * const const_value   = &const_value;
I've never really understood why people put const before the type, to me the following is far more obvious:

  void const* const_pointer = &const_pointer;
  void* const const_value = &const_value;

Re: Some Obscure C Features

#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.

Re: Some Obscure C Features

#127

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() {…

My assembly knowledge is limited, but it looks like both const_pointer and const_value get put into .rodata (read-only data). In both cases you're trying to change what is at the memory location that the pointer points to, in the first case it's the pointer that's in .rodata so you can change what it points to, but in the second case it's the value that's in .rodata so you can't change it.

Edit: Actually I don't think the pointer is put anywhere, rather its value is stored in .data (non-read-only data) so it can be mutated without issue. Again though, my assembly isn't amazing.

Re: Some Obscure C Features

#130
post #118
post #13

The preprocessor trick of passing function macros as parameters is not that obscure. I have seen it used and I've used it myself. It is very useful when you have a list of static "things" that you need to operate on. Say I have a static list of names and I would like to declare some struct type for each name. I also would like to create variables of these structs at some point, and I would always do so for the entire…

Here's one of my favorites for dealing with logging repetitive enum names and the like: enum foo { FOO_THING_ONE, FOO_THING_TWO, FOO_THING_THREE, ... FOO_THING_SEVEN_HUNDRED }; // Using concatenate '##' and stringify '#' operators #define FANCYCASE(X) case FOO_THING_##X: str=#X; break const char *foo_to_str(enum foo myFoo) { char *str; switch(myFoo) { FANCYCASE(ONE); FANCYCASE(TWO); FANCYCASE(THREE); ... FANCYCASE(SE…

You lose your ability to grep, though.
Post reply on HN