3

In the source-code for nanodns, there is an atypical use of the ternary operator in an attempt to reduce the size of the code:

/* If the incoming packet has an AR record (such as in an EDNS request),
 * mark the reply as "NOT IMPLEMENTED"; using a?b:c form to save one byte*/
q[11]?q[3]|=4:1;

It’s not obvious what this line does. At first glance, it looks like it is assigning a value to one of two array elements, but it is not. Rather, it seems to be either or’ing an array element, or else, doing nothing (running the “command” 1).

It looks like it is supposed to be a replacement for this line of code (which is indeed one byte longer):

if(q[11])q[3]|=4;

The literal equivalent would be this:

if (q[11])
  q[3]|=4;
else
  1;

The ternary operator is typically used as part of an expression, so seeing it used as a standalone command seems odd. Coupled with the seemingly out of place 1, this line almost qualifies as obfuscated code.

I did a quick test and was able to compile and run a C(++) program with data constants as “command”, such as void main() {0; 'a'; "foobar"; false;}. It seems to be a sort of nop command, but I cannot find any information about such usage—Google isn’t very amenable to this type of search query).

Can anyone explain exactly what it is and how it works?

Community
  • 1
  • 1
Synetech
  • 9,643
  • 9
  • 64
  • 96

2 Answers2

5

In C and C++ any expression can be made into a statement by putting ; at the end.

Another example is that the expression x = 5 can be made into a statement: x = 5; . Hopefully you agree that this is a good idea.

It would needlessly complicate the language to try and "ban" some subset of expressions from having ; come after them. This code isn't very useful but it is legal.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    Not just complicate - a bunch of functions would become nearly unusable, like `printf` - can you imagine writing `int outputted = printf("...");` every single time? :) – Amadan Sep 17 '15 at 04:33
  • `In C and C++ any expression can be made into a statement by putting ; at the end.` And naturally, POD types (“built-ins”) are all expressions… that makes sense. Of course, they don’t necessarily mean anything right? I guess you need to know these kinds of details for obfuscation code-golf. I would have thought that objects would need to be specifically written to support being used as an expression, but apparently they don’t; that’s built in. I just tested with `myclass;`. It almost reminds me of higher-level languages like Python and Ruby with code like `1.to_s`. – Synetech Sep 18 '15 at 06:52
2

Please note that the code you linked to is awful and written by a really bad programmer. Particularly, the statement

"It is common practice in tiny C programs to define reused expressions to make the code smaller"

is complete b***s***. That statement is where things started to go terribly wrong.

The size of the source code has no relation to the size of the compiler executable, nor any relation to that executable's memory consumption, nor any relation to program performance. The only thing it affects is the size of the source code files on the programmers computer, expressed in bytes.

Unless you are programming on some 8086 computer from mid-80s with very limited hard drive space, you never need to "reduce the size of the code". Instead, write readable code.

That being said, since q is an array of characters , the code you linked is equivalent to

if(q[11])
{
  (int)(q[3] |= 4);
}
else
{
  1;
}

Where 1 is a statement with no side effect, it will get optimized away. It was only placed there because the ?: operator demands a 3rd operator.

The only difference between if statements and the ?: operator is subtle: the ?: implicitly balances the type between the 2nd and 3rd operand.

To increase readability and produce self-documenting code, the code should get rewritten to something like

if (q[AR_INDEX] != 0)
{
  q[REPLY_INDEX] |= NOT_IMPLEMENTED;
}

As a side note, there is a bug here: q[2]|=128;. q is of type char, which has implementation-defined signedness, so this line is potentially disastrous. The core problem is that you should never use the char type for bit-wise operations or any form of arithmetic, which is a classic beginner mistake. It must be replaced with uint8_t or unsigned char.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    It is particularly ironic that if the code's author is so worried about source-code size that they chose to use an obfuscated form of a statement to save one byte, they then add `using a?b:c form to save one byte` in a comment -- these 33 bytes probably wipe out anything saved by obscure code! – TripeHound Sep 17 '15 at 09:06
  • @TripeHound, that is the expanded, explained version of the code. The [“minified” version](http://maradns.blogspot.ca/2010/08/nanodns-updated.html) doesn’t contain any comments (except an optional one at the start explaining the license). – Synetech Sep 18 '15 at 06:39
  • @Lundin, that specific code was only meant to be a minimal DNS server. Their goal was to [“minify”](http://maradns.blogspot.ca/2010/08/nanodns-updated.html) the [fuller version](http://samiam.org/software/microdns.html), like as code-golf. But good catch with the bug, and thanks for the explanations. – Synetech Sep 18 '15 at 06:49
  • @Synetech Still doesn't explain why they would reduce the source code itself. It actually does seem like that person who wrote the code doesn't understand the difference between source code size and binary executable size. – Lundin Sep 18 '15 at 06:57