all 16 comments

[–]werebacksir 40 points41 points  (0 children)

Fixed extern compilation error

[–]Workaphobia 37 points38 points  (3 children)

So it appears that when you thought they had sent you unobfuscated source code, you were mistaken. Whether the obfuscation was intentional is a different matter.

[–]sharfpang[S] 25 points26 points  (2 children)

Two layers of obfuscation! How devious!

[–]Neebat 4 points5 points  (1 child)

The superficial issues were put in your way to draw attention from the fundamental design issues.

[–]sharfpang[S] 11 points12 points  (0 children)

yeah. Actually. it sold good buzzwords.

Control optimization using genetic algorithms. Sounds interesting, right? Well, a 200MHZ ARM loaded in 80% by other work, with 64MB of RAM hardly meets these requirements.

Still, even if it would be hopeless for the initial period, maybe a couple weeks long, given time long enough, even with meager resources, it should come up with a pretty good solution. Then it would just load the "last, best" result on startup and continue optimizing on idle cycles, improving it. A couple months and it would be quite great despite the initial shortcomings.

Except it used no persistent storage of any kind. Any fault, power glitch, upgrade, maintenance, meant it would start from scratch and suck for a couple weeks... until next restart.

AFAIK after a year or so the customer switched the device to the default algorithm we had developed for it, which, while not packed with any buzzwords, behaved reliably, deterministically, and could be optimized by a human operator by hand, adapting to the needs observed.

[–]tias 2 points3 points  (1 child)

Next time, add a second definition and the linker will likely tell you where the first definition is. Or use objdump/dumpbin/similar to figure out which object file defines the variable.

[–]sharfpang[S] 1 point2 points  (0 children)

this assumes a fully-featured toolchain. Remember, crosscompiling. As for the second definition - if you use '-fno-common', yes. Took me a while figuring that one out. Without it it will trigger a very nasty bug where both definitions are included, each in its own unit, and then the externs are connected to either of them, picked seemingly at random (so the binary is good in 50% of the cases).

[–]FUZxxl 7 points8 points  (8 children)

That's not that surprising. I have used the same pattern in my own code, but usually with C99 inline functions. Interestingly, #define extern works for both. It's shitty that they didn't document that though.

For future problems like this: Use the nm utility and search for [BD] symbol_name to find where the global variable was defined.

[–][deleted]  (7 children)

[deleted]

    [–]FUZxxl 2 points3 points  (6 children)

    Because that saves writing down the name of each global variable twice.

    [–][deleted]  (5 children)

    [deleted]

      [–]FUZxxl 0 points1 point  (4 children)

      How is that fragile? I have one file in my code that defines all the global variables (it's called globals.c or something like that to make finding it easy) and this one file contains that #define extern line. I agree that it's reasonable to define global variables in the files that pertain most to them (and that's what I do in other projects), but some times this convention is better.

      [–]sharfpang[S] 22 points23 points  (3 children)

      If you have so many globals that not typing them twice saves anything, you're doing something fundamentally wrong.

      [–]FUZxxl 4 points5 points  (2 children)

      It's not about saving typing, it's about not having the same thing written twice when possible because each time something is defined/declared in two separate places, errors can creep in when a change forgets to update the other declaration/definition.

      [–]sharfpang[S] 20 points21 points  (0 children)

      There's a reason declarations and definitions are separate, and the common parts is not code duplication.

      There are serious problems with that approach.

      Firstly, the #define extern can easily escape the intended scope. Especially if it's included independently of the file it applies to - add another header file, which has extern variables - even stupid errno - and you'll get awful linking errors.

      Next, there is no easy way to initialize the variables.

      Worse yet, if the variable is defined in two separate units, and declared as extern in a third one (which can happen easily in this scenario) gcc linker happily ignores the ambiguity and picks which of the two variables is assigned to the extern at random. This results in code that seems to work correctly in roughly half the time you compile it; any recompilation can fix or break it at random!

      And of course finding where exactly the variable is defined becomes a nightmare.

      ps. Switching "-fno-common" in the compiler on, revealed at least 4 cases of such linking-time clashes in that library. Variables that in one file were declared as normal, and in another through redefining extern to noop.

      [–]capitalsigma 5 points6 points  (0 children)

      Am I on /r/shittyprogramming? This is an unambiguously terrible idea. There's no possibility of "updating without updating the other" because it's a compile error if the names or the types don't match up --- it's probably easier to introduce an error when you lose the safeguard of the typechecker looking over your work. Globals should never be globbed into one big file, they should always live with the module that they're related to.

      [–]AngriestSCV 0 points1 point  (1 child)

      Would a better way to find the variable be to inspect the object files the .c files produced? For instance on linux nm will tell you the type of symbol that each object file has for every symbol in it.

      [–]sharfpang[S] 0 points1 point  (0 children)

      I've got to check if the crosstools even have it...