Live data from Hacker News

Some Obscure C Features

multun.net

111–120 of 147 posts

Re: Some Obscure C Features

#111

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?

I'm not certain, but the `extern` variant probably doesn't reserve space at compile-time; it just says "the linker will know where to find these". So resolving those symbols might need to wait until link-time. The tentative definitions probably do reserve space (and hence an immediately-known address), and the later true definitions just supply the initial value to put in that space.

Re: Some Obscure C Features

#112

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!

That's what the static keyword means in those array declarators. void func(int x[static 10]); must be called with an argument that is a pointer to the start of a big enough array of int. I can't get recent GCC or Clang to warn on violations of this, though.

There are cases where the compiler can't enforce it:

    void foo(int *p)
    {
      func(p);
    }
How can the compiler know if `p` points to space for 10 integers?

Re: Some Obscure C Features

#115
post #109

Earlier quoted context omitted.

IIRC these are a legacy of BCPL

They weren't in K&R. I recall a story about a standardization meeting, on the way to ANSI C, where representatives from some European(?) country that didn't have some of the necessary punctuation on their country-specific keyboards, essentially snuck the trigraphs into the spec when the other representatives weren't looking.

Trigraphs are also necessary on IBM Z if you don't want to (or can't) switch from most standard EBCDIC code pages to the special C code page

Re: Some Obscure C Features

#116
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…

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()
  {
      printf("%p\n", const_pointer);
      *(int*)const_pointer = 0;
      printf("%p\n", const_pointer);
  
      printf("---------------------------\n");
  
      printf("%p\n", const_value);
      *(int*)const_value = 0;
      printf("%p\n", const_value);
      return 0;
  }
  
  Result:
  
  $ gcc test.c
  test.c:4:30: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
  void * const const_value   = &const_value;
                              ^

  $ ./a.out
  0x55b29ebfc010
  0x55b200000000
  ---------------------------
  0x55b29ebfbdb8

  Command terminated

As to why the first one doesn't also result in a segfault, I don't know.

Re: Some Obscure C Features

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

Re: Some Obscure C Features

#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(SEVEN_HUNDRED);
      }
      return str;
  }
Post reply on HN