Live data from Hacker News

How to Think About Variables in C

denniskubes.com

61–66 of 66 posts

Re: How to Think About Variables in C

#61

Earlier quoted context omitted.

uint8_t foo[4]; *(uint32_t*)foo = 0; Besides even without strict aliasing, the above is not at all guaranteed to work since not all architectures support unaligned loads. So, the interesting thing about this example is that it does work. It's in fact very, very difficult to find a platform where that example won't work (i.e. crashes the program). For example, any C library involving image manipulation is likely going…

> It's in fact very, very difficult to find a platform where that example won't work No, it isn't. Many ARM processors will bus error on that code if (foo & 3) != 0. I believe PowerPC doesn't do unaligned word reads either... It quite often has to do with the memory controller and not with the particular processor, though I believe x86 has to support unaligned reads. I've certainly worked first hand with ARMs that di…

That's interesting. What causes the bus error?

Would

  uint8_t foo[4];  *(uint32_t*)(&foo[0]) = 0;
also result in a bus error? Why?

Re: How to Think About Variables in C

#62

There are some subtle problems with the model as explained in this article. If you use this as your mental model, you will probably run afoul of undefined behavior without realizing it. If you read the C standard, you'll notice it doesn't talk much about "memory" (the word only appears 13 times in C99); it mostly talks about "objects" (mentioned 735 times in C99). These objects aren't OO-objects -- obviously C doesn'…

If a variable were just a memory address and assignment were just a memory copy, this would be a valid operation. It's a valid operation regardless of whether a standards body says it's not. uint32 x = 5; uint16 y = *(uint16*)&x; The effect is to set y to the first two bytes of memory from x. Values assigned to x are serialized into memory in either big endian or little endian order. Those are the only two cases you…

> It's a valid operation regardless of whether a standards body says it's not.

All the world's a VAX, sure. Don't mind the next generation of hardware coming down the pike and the next wave of compiler optimizations.

http://catb.org/jargon/html/V/vaxocentrism.html

Re: How to Think About Variables in C

#63

Earlier quoted context omitted.

> It's in fact very, very difficult to find a platform where that example won't work No, it isn't. Many ARM processors will bus error on that code if (foo & 3) != 0. I believe PowerPC doesn't do unaligned word reads either... It quite often has to do with the memory controller and not with the particular processor, though I believe x86 has to support unaligned reads. I've certainly worked first hand with ARMs that di…

That's interesting. What causes the bus error? Would uint8_t foo[4]; *(uint32_t*)(&foo[0]) = 0; also result in a bus error? Why?

That's the same thing, so yes, if foo is unaligned then it will cause a bus error. It causes it because the code is generate a store word assembly instruction (as opposed to store byte) and if the address is not aligned to 4 bytes then the memory controller hardware will raise a bus error.

Notice I keep saying "if the address is unaligned". The insidious part is that it probably will work for a while since it's likely that your "foo" array will happen to be aligned. But add one uint8_t variable to your structure or stack frame or wherever "foo" is defined and things could shift and suddenly it starts causing bus errors. It can be a very annoying type of heisenbug.

And bus errors are actually a good thing. I believe I've used hardware (an ARM or an SH2, can't remember) where the memory controller just ignored the last 2 bits during whole word reads and writes (which works fine as long as you only read aligned words). So if run your code on that hardware it doesn't give you an error, it just subtly "corrupts" your data. Yay!

Re: How to Think About Variables in C

#64

Earlier quoted context omitted.

Yes, it is an invalid program. The antidote is to fix it, not to jigger it in a way that happens to work. The memory barrier is not "necessary" -- it is not even a correct fix. Even with a memory barrier as you added it, it is still an invalid program that invokes undefined behavior. The memory barrier may have coincidentally fixed the problem on your system, but there is still no guarantee it will work on another ar…

but there is still no guarantee it will work on another architecture, another compiler, or even another version of the same compiler. My point is that it is guaranteed to work. A memory barrier guarantees that all memory operations before the barrier take effect before any operations after the barrier. I think this whole exchange is fascinating because it illustrates two completely different philosophies to hacking.…

It isn't guaranteed to work even with the memory barrier, because the undefined behavior is not merely an ordering problem. The problem is that merely accessing the object through the wrong kind of pointer breaks the rules and gives the compiler a license to do anything.

There is a time and place to break the rules, but it is a calculated risk. It can only be considered "safe" if you make assumptions about your environment (platform, toolchain, etc). You're vulnerable if any of those assumptions change. The things people considered "safe" 10 years ago aren't "safe" any more. But the people who followed the rules never have to change their approach.

For what it's worth, a cheaper barrier in this case (if you were going to take that route) is just a compiler barrier like __asm__ __volatile__ (""); (see: http://en.wikipedia.org/wiki/Memory_barrier#Out-of-order_exe...). There's no need to emit an actual CPU barrier.

Thanks about Scribe; it's a labour of love.

Re: How to Think About Variables in C

#65

What other mental models do people use to think about variables and memory? I would like to hear about them.

In higher-level languages I don't consciously think about how they're represented in memory.

You can thank the compiler writers for that. You know, people who work in "lower level languages"

Re: How to Think About Variables in C

#66

Earlier quoted context omitted.

but there is still no guarantee it will work on another architecture, another compiler, or even another version of the same compiler. My point is that it is guaranteed to work. A memory barrier guarantees that all memory operations before the barrier take effect before any operations after the barrier. I think this whole exchange is fascinating because it illustrates two completely different philosophies to hacking.…

It isn't guaranteed to work even with the memory barrier, because the undefined behavior is not merely an ordering problem. The problem is that merely accessing the object through the wrong kind of pointer breaks the rules and gives the compiler a license to do anything. There is a time and place to break the rules, but it is a calculated risk. It can only be considered "safe" if you make assumptions about your envir…

I think that your original suggestion - use memcpy is a better solution then volatile.

It looks like the following code would work:

  #include 
  #include 

  void f(volatile uint32_t *x, volatile uint16_t *y) {
    *x = 5;
    printf("%d\n", *y);
  }

  int main() {
    volatile uint32_t x = 10;
    f(&x, (volatile uint16_t*)&x);
  }
And the compiler is guarantied () to issue store op on x = 5 and consecutively load op on y, but the code is looking pretty ugly.

() assuming no alignment problems

Post reply on HN