3

Does anyone know what does &- in C programming?

limit= address+ (n &- sizeof(uint));

  • 2
    `&` bitwise and operator; `-` unary minus operator – pmg Sep 06 '21 at 11:52
  • 2
    To understand, try different spacing: `limit = address + (n & -sizeof(uint));` – Craig Estey Sep 06 '21 at 11:53
  • 2
    Same as `n & ( - sizeof(uint))` – klutt Sep 06 '21 at 11:53
  • 2
    @Adil.Kolenko You can meet even the following operator &-- or &++ in C.:) Or for example operator +++ or ---.:) New operators in C appear due to a bad programming style. – Vlad from Moscow Sep 06 '21 at 11:56
  • @VladfromMoscow `if (!limit) return;`, in this case `!` , is it bitwise NOT? – Adil.Kolenko Sep 06 '21 at 12:08
  • 2
    @Adil.Kolenko ! is the logical negation operator. – Vlad from Moscow Sep 06 '21 at 12:10
  • 1
    bitwise not is `~`; `!` is logical not ... `!a` or `!!a` is either `0` or `1` whatever the type and value of `a` – pmg Sep 06 '21 at 12:50
  • @pmg What does `if (!limit) return;` mean? – Adil.Kolenko Sep 06 '21 at 13:04
  • 1
    same as `if (limit == 0) return;` with the proper `0` of the right kind to be compared with `limit` (maybe it's good enough to rely on compiler doing the right conversion if needed). – pmg Sep 06 '21 at 13:05
  • 2
    It typically means that you are reading some joke code. Similarly `-->` and `+++` operators don't exist either. – Lundin Sep 06 '21 at 13:20
  • @Lundin does it mean all comments are wrong? `&-` -command doesnt exist? – Adil.Kolenko Sep 06 '21 at 13:27
  • 1
    @Adil.Kolenko `&-` is not a "command" or an operator - it's just poorly spaced code. **Usually** people would write the code with a space between the `&` and the `-` - Lundin means that writing the code like `&-` is usually done as a joke (or code golf, or whatever). Similarly, `-->` is usually written as `-- >` (https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c-c?rq=1) – Daniel Kleinstein Sep 06 '21 at 13:42
  • 5
    IMO code like `limit= address+ (n &- sizeof(uint));` is probably from a programmer trying to show off by being *clever*. As in, "Look at my *clever* code! I wrote it, and you can't figure it out! Aren't I *clever*!" Nope, you're not. Truly brilliant code evokes responses like, "That's so easy and obvious! Why didn't I think of that?" and not "WTF is this?!?!" Code that evokes the latter - as in this case - isn't *clever*. It's **STUPID**. It's bug-prone and wastes time and effort both upon creation and when someone tries to understand it. Making things harder is the opposite of clever. – Andrew Henle Sep 06 '21 at 14:28
  • 3
    @AndrewHenle The most accurate code quality metric is [WTF/minute](https://i2.wp.com/commadot.com/wp-content/uploads/2009/02/wtf.png?resize=550%2C433) :) – Lundin Sep 06 '21 at 14:37
  • @AndrewHenle is there simple version of the code in the post (`limit= address+ (n &- sizeof(uint));`)? Can I rewrite it ? – Adil.Kolenko Sep 07 '21 at 05:53

2 Answers2

11

This isn't really one operator, but two:

(n) & (-sizeof(uint))

i.e. this is performing a bitwise and operation between n and -sizeof(uint).


What does this mean?

Let's assume -sizeof(uint) is -4 - then by two's complement representation, -sizeof(uint) is 0xFFFFFFFC or

1111 1111 1111 1111 1111 1111 1111 1100

We can see that this bitwise and operation will zero-out the last two bits of n. This effectively aligns n to the lowest multiple of sizeof(uint).

Daniel Kleinstein
  • 5,262
  • 1
  • 22
  • 39
  • 2
    it *does not* depend two complement. Unary `-` of `unsigned` is `unsigned`. See 6.5.3.3p3. Next according to 6.3.1.3p2 the result will set to *unsinged* `2^N-4` which is `111...1100` independently of representation of signed integers. (N - is number of bits in `unsigned`) – tstanisl Sep 06 '21 at 12:21
  • 1
    @tstanisl Hehe, cool! Though I'm not entirely sure that this means that we can assume the value will be `1111...1100` - at any rate I'm leaving two's complement in the answer because I think it's relevant for beginners' understanding. I'll edit your comment in, thanks :) – Daniel Kleinstein Sep 06 '21 at 12:31
  • 1
    6.3.1.3p basically says that negative values are converted to unsigned by repeatedly adding UINT_MAX (i.e. 2^32) until it gets into `0..UINT_MAX-1` range. And unsigned numbers have natural encoding for binary numbers. – tstanisl Sep 06 '21 at 12:37
  • @tstanisl In this case `SIZE_MAX`. But also note that `UINT_MAX` is defined as 2^n - 1 so in range will be `0...UINT_MAX`. – Lundin Sep 06 '21 at 14:35
  • @Lundin, you're right. I've meant adding SIZE_MAX+1 – tstanisl Sep 06 '21 at 14:38
3

&- is the binary bitwise AND operator written together with - which is the unary minus operator. Operator precedence (binary & having lowest precedence here) gives us the operands of & as n and -sizeof(uint).

The purpose is to create a bit mask in a very obscure way, relying on unsigned integer arithmetic. Assuming uint is 4 bytes (don't use homebrewed types either btw, use stdint.h), then the code is equivalent to this

n & -(size_t)4

size_t being the type returned by sizeof, which is guaranted to be a large, unsigned integer type. Applying unary minus on unsigned types is of course nonsense too. Though even if it is obscure, applying minus on unsigned arithmetic results in well-defined wrap-around1), so in case of the value 4, we get 0xFFFFFFFFFFFFFFFC on a typical PC where size_t is 64 bits.

n & 0xFFFFFFFFFFFFFFFC will mask out everything but the 2 least significant bits.

What the relation between these 2 bits and the size of the type used is, I don't know. I guess that the purpose is to store something equivalent to the type's size in bytes in that area. Something with 4 values will fit in the two least significant bits: binary 0, 1, 10, 11. (The purpose could maybe be masking out misaligned addresses or some such?)

Assuming I guessed correct, we can write the same code without any obfuscation practices as far more readable code:

~(sizeof(uint32_t)-1)

Which gives us 4-1 = 0x3, ~0x3 = 0xFFFF...FC. Or in case of 8 byte types, 0xFFFF...F8. And so on.

So I'd rewrite the code as

#include <stdint.h>

uint32_t mask = ~(sizeof(uint32_t)-1);
limit = address + (n & mask);

1) C17 6.3.1.3

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type60)

Where the foot note 60) says:

The rules describe arithmetic on the mathematical value, not the value of a given type of expression.

In this case repeatedly subtracting SIZE_MAX+1 from 4 until the value is in range of what can fit inside a size_t variable.

Lundin
  • 195,001
  • 40
  • 254
  • 396