News for hackers. Stuff that matters.
Re:Some Obscure C Features by compi on Thursday August 22, 2019 @11:33PM (#20774329)
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", *((void**)true_ptr));
    printf("%p\n", *((void**)*((void**)true_ptr)));
You get the same pointer:

    0x5555fefe715b
    0x5555fefe715b
    0x5555fefe715b
    0x5555fefe715b
I still think that it's neat that, even with ASLR, you have an address at compile time that you know won't collide with address space of malloc results, or the address space of your stack.

Also you can declare the pointer as const and the value it points to as const and, if your kernel faults on writing to readonly memory pages, you get a buggier version of a NULL pointer that only segfaults on write.

Also it takes a second to figure out why the position of the const matters even though the pointer's value is the value of the pointer, and why only one of these segfaults on write:

    const void * const_pointer = &const_pointer;
    void * const const_value   = &const_value;
Re:Some Obscure C Features by Sean1708 on Friday August 23, 2019 @09:31AM (#20777346)

  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 by davelee on Friday August 23, 2019 @12:27AM (#20774511)
I've used this feature to create constants that are unique IDs.
Re:Some Obscure C Features by shultays on Friday August 23, 2019 @02:51AM (#20775064)
Wouldnt count macro work as well?
Re:Some Obscure C Features by davelee on Friday August 23, 2019 @09:58AM (#20777658)
I think that would be per file/translation unit. These values are unique across a linked binary.
Re:Some Obscure C Features by cryptonector on Friday August 23, 2019 @01:45AM (#20774824)

  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).
Re:Some Obscure C Features by wizzairflyer on Friday August 23, 2019 @05:41AM (#20775850)
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 <stdio.h>
  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 by nybble41 on Friday August 23, 2019 @02:15PM (#20780662)
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.
Re:Some Obscure C Features by Sean1708 on Friday August 23, 2019 @09:38AM (#20777449)
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 by paulrpotts on Friday August 23, 2019 @02:26PM (#20780821)
Some early compilers such as THINK C for the Mac weren't very strict about typing when taking the address of an object. So for example you could do:

unsigned int x;

unsigned int * x_addr = &x;

unsigned int x_addr_addr = &(&x);

(or arbitrarily many levels of "address-of") and you'd just get the same address.