Ways to clean up after yourself: C (no builtin exception handling)

[This is probably common knowledge that are repeated in many different places, but I want to arrange it in the perspective that helps my other article to explain how this influence the idea of ContextManager in Python and why ContextManager is still a clumsy way and there is a much neater way to do tackle this classic cleanup problem]

In C, there’s no built-in exception handling. Yet the goal for cleanups is for all manually checked and handled conditions (‘exceptions’) to land exactly in the same graveyard where the resources stands a chance to be released before the program ends. This screams goto (or longjump which is a non-local goto that can march outside the current function) and indeed it’s the only legit use of the goto statement I know that doesn’t make the code more error prone and confusing by littering the end of all your loops with if(error){break;}.

With this feedback approach, all hell breaks loose if you later add another layer and forget to add this. The mistake will break the feedback chain and the code continues to run in the layer after the end of the the while loop you forgot to place the check in, which is an insidious bug if the unwanted execution are benign under most situations.

The break clause will also become meaningless (and compiler invalid) if you convert your loops to non-loops or when you work in the top layer which is likely not inside a loop (even if you are in a bare metal embedded system with a while(0) loop, you don’t want to break that either).

break is a black-list approach which denies the rest of the code in the loop from running when the first error struck. Without break, you can do the reverse (the white-list approach) and put all code after the first check in if(!error) check blocks to authorize their execution instead

One hack to use break statement at the top-layer (no-loop) is to wrap the top layer with a do{BLOCK}while(false); loop which runs once, but the intention is not intuitive from the code so I wouldn’t do this to other programmers who don’t know the idiom without making a TRY-CATCH-FINALLY macro.

// Messy approach without do-while loop wrapper hack in top layer

// Convention: error=0 (false) means success
// The error code matches the check# it fails in this example
int error = 0;   
if( int* f = grab_resource_and_spit_zero_if_fail() )
{
  // This is hell of messy if you are not in a loop
  // that can take advantage of break-statement
  //
  // If you don't use breaks (which require it to be in a loop), 
  // you have to explicitly surround all code in if(!error) blocks
  ...
  // Approach 1: Nesting 
  // Upside:   Visually draws out what the logic relation is
  //           when you're doing checks all over the place
  //           It's hard to get it conceptually wrong
  //           (i.e. can debug blindly with mere semantics)
  // Downside: more checks means more nests 
  //           you either have excessive indentation
  //           or have fun tracking brackets
  if( !is_fail_1() )
  {
    ...
    if( !is_fail_2() )
    {
      ...
      if( !is_fail_3() )
      {
        (you get the idea)
        ...
      } else { error=3; }
    } else { error=2; }
  } else { error=1; }

  // Approach 2: Linear approach
  // Idea:       Surround everything under if(!error) check.
  //             Error code will stick to the first error as
  //             any non-zero error will short-circuit the 
  //             checks after it so the original error code stays
  // Upside:     No nesting. Easy to follow to recipe consistently
  // WARNING:    Thou shalt not be tempted to modify error code 
  //             in if(!error) blocks!
  // Downside:   If you violate the cardinal rule above, unintended
  //             chunks of code in if(!error) blocks might run and
  //             it's hard to debug/discover
  if(!error)
  {
    ...
  }
  // this idiom is a branchless way to do 
  // if(!error){ if(is_fail_13){error=13;} }
  // The error==0 should be placed in front to
  // take advantage of the short-circuit evaluation
  // to avoid actually running the check if there's
  // pre-existing error
  error = (error>0)*error + (error==0 && !is_fail_13())*13
  if(!error)
  {
    ...
  }
  error = (error>0)*error + (error==0 && !is_fail_14())*14
  if(!error)
  {
    ...
  }
  error = (error>0)*error + (error==0 && !is_fail_15())*15
  if(!error)
  {
    ...
  }
  // Now you are in the first loop so you are allowed to use break
  for(...)
  {
     ...
     (is_fails 16 .. 41)
     ...
     if( is_fail_42() )
     {
        error = 42;
        break;
     }
     ..
     while(...)
     {
       ... 
       (deep down in the nest)
       ...
       (is_fails 43 .. 101)
       ...
       if( is_fail_102() )
       {
          error = 102;
          break;
       }
       ...
     }
     if(error) { break; }
    ...
  }
  if(error) { break; }
  ...
  clean_the_f_up(f);
} 

// goto approach
if( int* f = grab_resource_and_spit_zero_if_fail() )
{
  ...
  (is_fails #1 .. 18)
  ...
  for(...)
  {
     ...
     (is_fails 19 .. 41)
     ...
     if( is_fail_41() )
     {
       goto graveyard;
     }
     ...
     while(...)
     {
       ... 
       (deep down in the nest)
       ...
       (is_fails 43 .. 101)
       ...
       if( is_fail_102() )
       {
          goto graveyard;
       }
       ...
     }
    ...
  }
  ...
  graveyard:
    clean_the_f_up(f);
} 

By jumping to the graveyard, we don’t need to litter the code with a long chain of error message/signal feedback and/or guard all chunks of code with if(!error) blocks, which is messy because it’s basically re-inventing a lightweight custom exception handling infrastructure that propagates the fault back to the top and give the intermediate layers a chance to intercept it.

As long as you are not using the goto approach to do complicated maneuvers and keep it simple: all faults go to the same bucket, no ifs-and-buts or detours (i.e. no code elsewhere/in-between can intercept the flow), it isn’t spaghetii code: there are no complicated code flow graphs, just every branch pointing to the same destination in one step. You don’t need to feel guilty about using the goto approach if your error handling flow is like this:

Loading

Navigating between oscilloscope-land (Volts) and RF-land (dBm)

Have you ever been annonyed by the dBm and V units when you try to measure output from your benchtop signal generator (or arbitrary waveform generator, or ARB) with a spectrum analyzer, or measure a RF signal (sine) generator with an oscilloscope?

TLDR

For $50\Omega$ systems only. The fastest way is to work with squared voltages in linear scale with the provided ‘magic’ scaling factor (10 for amplitude and 20 for rms) and not mess with factorization in the log (dB) or anti-log ($10^{\frac{\mathrm{dB}}{10}}$) process:

P[\mathrm{mW}]=10V_{pp}^2=20V_{rms}^2
\mathrm{dBmW}=\mathrm{dB}\left(10V_{pp}^2\right)=\mathrm{dB}\left(20V_{rms}^2\right)

This happens a lot during calibration. An oscilloscope needs a 500Mhz signal at $600mV_{pp}$ to test if the front-end rolls off too early (i.e. have a lower bandwidth than advertised), yet my \$12k RF signal generator (20Ghz sweeper) only does dBm, and in coarse increments of 0.1dBm.

  • Decibel is supposed to be a dimensionless quantity, aka just a ratio.
  • The confusion came from people in each field abusing notations by skipping the reference quantity suffix after dB. e.g. RF people write dBW and dBmW (a dimensioned quantity with loaded assumptions of a $50\Omega$ scaling) as dB and dBm (which would naturally read as a dimensionless ratio)
  • When RF people talk about decibels, especially when they abuse notations by not mentioning the reference (basis) quantity, you can be pretty sure that they are talking about ratios between power quantites (dBW), not voltage quantities (such as dBV).
  • There’s no such thing as a $20log_{10}$ decibel unit. It’s always $10log_{10}$ dimensionless. The factor of 2 came from the square (power of 2) factoring out of logarithms as a multiple of 2 when ratios of power are expressed as ratios of voltages.
  • Always work in power ratios to avoid the confusion. dB is dimensionless but often the unspoken rule is that it’s a ratio of power quantities. Routines or formula like mag2db really mean magnitude_squared_in_dB because it’s doing $10log_{10}(A^2)$.
  • The other advantage of actually squaring the term first before taking the log is that you do not have to check the input for negative values as the squaring operation made it absolute value (square). If you are doing a logarithm, an extra multiplication (over an addition) is insignicant in terms of computational burden.
  • $V_{rms}$ maps directly to power quantities, scaled by the impedance (which is typically $50\Omega$). Everything else involves an extra crest factor (which is $\sqrt{2}$ for sinusoids)
Continue reading

Loading

TL866CS and TL866A recovery mode

I was playing around with my TL866 programmer trying to refresh the firmware and needed to get to the recovery mode. However, it seems like the recovery mode info on http://www.autoelectric.cn/en/note.html is incorrect for 2014 design (the one I have). I tried the 240\Omega as recommended with a decade box and it doesn’t work .

Usually my guess that if somebody is going to set a recovery mode in a micro-controller to be enabled with some hardware path to the power supply rail with a small resistor, it’s just saying that he wants you to pull the pin high.

The said pin is a digital I/O line so I took it as a GPIO pin used for input (I might be wrong)

I measured the voltage of the said regulator with a decade box. Interesting that if I flip the resistance while the unit is on, it occasionally remembers the last state when I plug it in again! (EDIT: There is a Schmitt Trigger buffer in between. The buffer type says ST for RC1. Doh!)

Here are some of my measurements with a decade box:

Continue reading

Loading

54855A/54854A Oscilloscope won’t power on

Recently I got a 54855A oscilloscope sent back to me for service under the 1 year warranty I underwrote for most of the unit I’ve sold direct. The unit would not turn on at all after sitting for a long time.

I looked up the forum and it turns out other people had this problem with a certain generation of Infiniiums and sometimes changing power supply or motherboard would disturb the setup a little bit and the unit might power on again. When I tried to do that, the unit does boot a quite few times but the problem randomly came back again. This is frustrating as it’s a heisenbug. I almost thought I was done when the unit worked consistently for a week then it comes back. It just looked like something component was acting borderline and disturbing the setup and got it to click.

In the past I had 54830s that’s ‘fixed’ by changing either one of the motherboard or power supply, but turns out that those were flukes, but I didn’t get lucky with 54850s this time.

The customer gave me time to troubleshoot deeper instead of just getting something to work as a fluke by just blindly changing modules which might break down again at a random time if it’s a gravitating aging problem (i.e. if you figured out a problem, it tends to be a wave of manufactured gears that’ll trip on the the same issue as they age). So I spent a whole month troubleshooting through reverse engineering the circuit and nailed down the cause and the fix.

There’s a lot of mixed info going on in the forum with different modes of failure, but those might not be the real cause, as replacing components disturbs a set up that wasn’t supposed to be in that state in the first place and the unit is prone to get trapped into a bad state when the unit ages. The real fix is to plug the path to the bad state in the first place instead of rocking the boat hoping the new combination doesn’t trigger the bad state.

Continue reading

Loading

Agilent LCR meters: typing profile names painlessly without using instrument keypad

For newer LCR meters where you can label your measurement setups before saving, using the context menu to enter the names is a painful task, and it shortens the life of the keypads. There’s a quicker way to enter it: send GPIB commands (use Agilent I/O) to it. The command is:

DISPLAY:Line "Profile Name 1"

which you can replace the “Profile Name 1” with anything. Keep the double quotes so the parser won’t be confused by special characters such as minus/dash (-) signs.

If the unit supports LXI (i.e. network interface) such as E4980A, so you can send the GPIB commands through network using Agilent I/O Suite.

Loading