2

In the process of porting an HP-UX C++ application over to LINUX, I've noticed that there was a function of type int where not all code paths appear to return an integer value. When I compile and run the application under HP-UX (using the acc compiler for compilation), it returns a 0 through a codepath that does not explicitly state a return value. However, when I compile and run the same application under LINUX, I get a return value of -72 and the application presents an error consequently (this is intended when the return value is less than 0). I've noticed that classic C++ (a very old and outdated standard which the ACC compiler supports) deals with variable scoping a bit differently from standard C++ (which ACC unfortunately does not support). In classic C++, it appears that declaring an integer inside a forloop declaration

for( int index = 0; index < array.length; index++ )

the variable index is accessible outside the forloop, although I wouldn't know for sure whether the return statements are dealt with in the same way such that aCC recognizes that all code paths return a value.

The function i'm dealing with is available below:

int process_phase (const char *phase, const char *seg_type, const char *dist_target, const char *action_target, char *cmd) 
{
  char cmd2[MAX_STRING];

  printf( "I AM INSIDE THE PROCESS_PHASE\n" );

  if (TRACE_MODE) 
  {
    printf ("%s %s\n", MSG_LOOKUP("MSG_PHASE"), phase);
    printf ("%s %s\n", MSG_LOOKUP("MSG_SEG_TYPE"), seg_type);
    printf ("%s %s\n", MSG_LOOKUP("MSG_DIST_TARGET"), dist_target);
    printf ("%s %s\n", MSG_LOOKUP("MSG_ACTION_TARGET"), action_target);
    printf ("%s %s\n", MSG_LOOKUP("MSG_CMD"), cmd);
  }

  // Remove the pre- and post- prefixes
  const char *phase_ref = strchr(phase, '-');

  printf ("PHASE REF BEFORE PREFIX REMOVAL: %s\n", phase_ref );

  if (phase_ref) 
  {
    phase_ref++; 
  }
  else
  {
    phase_ref = phase;
  }

  printf ("PHASE REF AFTER PREFIX REMOVAL: %s\n", phase_ref );

  if (TRACE_MODE)
  {
    printf ("%s %s\n", MSG_LOOKUP("MSG_PHASE_REF"), phase_ref);
  }

  printf ( "==========TEST PHASE 1 BEGIN==========\n" );

  if (strcasecmp(phase_ref, "all_phases") != 0) 
  {
    if (DO_TRANSFER && strcasecmp(phase_ref, "transfer") != 0) 
    {
      printf ("IN ONE\n");
      return 0;
    }
    else if (DO_TAPE_GENERATION && strcasecmp(phase_ref, "tape_generation") != 0) 
    {
      printf ("IN TWO\n");
      return 0;
    }
    else if (DO_TAPE_EXTRACTION && strcasecmp(phase_ref, "tape_extraction") != 0) 
    {
      printf ("IN THREE\n");
      return 0;
    }
    else if (DO_PREPARATION && strcasecmp(phase_ref, "preparation") != 0)
    {
      printf ("IN FOUR\n");
      return 0;
    }
    else if (DO_DISTRIBUTION && strcasecmp(phase_ref, "distribution") != 0)
    {
      printf ("IN FIVE\n");
      return 0;
    }
    else if (DO_VERIFICATION && strcasecmp(phase_ref, "verification") != 0)
    { 
      printf ("IN SIX\n");
      return 0;
    }
    else if (DO_ACTIVATION && strcasecmp(phase_ref, "activation") != 0) 
    {
      printf ("IN SEVEN\n");
      return 0;
    }
    else if (DO_REMOVAL && strcasecmp(phase_ref, "removal") != 0)
    {
      printf ("IN EIGHT\n");
      return 0;
    }
  }

  printf ( "==========TEST PHASE 1 END==========\n" );

  // if (strstr(seg_type, envvar("SEGMENT_TYPE")) == 0 && strcasecmp(seg_type, "ALL") !=0) return 0;
  char tmp_seg_type[MAX_BUFFLEN];
  sprintf (tmp_seg_type, "_%s_", seg_type);

  printf( "tmp_seg_type: %s\n", tmp_seg_type );

  char tmp_envar_seg_type[MAX_BUFFLEN];
  sprintf (tmp_envar_seg_type, "_%s_", envvar("SEGMENT_TYPE"));

  printf( "tmp_seg_type: %s\n", tmp_seg_type );

  if ( strstr(tmp_seg_type, tmp_envar_seg_type) == 0 && strcasecmp(seg_type, "ALL") != 0 )
  {
    printf( "IN TEST PHASE TWO\n" );
    return 0;
  }

  char match_list[MAX_BUFFLEN];

  printf ( "==========TEST PHASE THREE BEGIN==========\n" );

  if ( DO_TRANSFER ) 
  {
    printf( "IN ONE\n" );
    sprintf( match_list, "DIST_NODES", dist_target );
  }
  else if (DO_TAPE_GENERATION) 
  {
    printf( "IN TWO\n" );
    sprintf( match_list, "HOST_NODES", dist_target );
  }
  else
  {
    printf( "IN THREE\n" );
    sprintf( match_list, "%s_NODES", dist_target );
    printf( "match_list: %s\n", match_list );
  }

  printf ( "==========TEST PHASE THREE END==========\n" );

  char matched_nodes[MAX_BUFFLEN];
  get_env(match_list, matched_nodes);

  printf( "matched_nodes: %s\n", matched_nodes );

  word_sort_unique(matched_nodes);

  printf( "sorted_matched_nodes: %s\n", matched_nodes );

  const char *element_separator = " ";
  const char *curr_node_type;
  char *curr_node = strtok(matched_nodes, element_separator);

  printf( "curr_node: %s\n", curr_node );

  int whileiteration = 0;

 while (curr_node) 
 {
    printf( "WHILE LOOP ITERATION: %d\n", whileiteration );

    int node_idx;
    bool matched = false;

    for (node_idx=0; node_idx<node_count; node_idx++) 
    {
      if (strcmp(curr_node, node_table[node_idx]) == 0) 
      {
        matched = true;
        break;
      }
    }

    if (matched) 
    {
      if (strcasecmp(action_target, "TARGET") == 0) 
      {
        if (indent[node_idx][0] == 0) 
        {
          fprintf (outfile[node_idx], "remsh %s \"\n", curr_node);
          indent[node_idx] = "  ";
          fprintf (outfile[node_idx], "%s. %s/site_profile\n", indent[node_idx], envvar ("TOOLS_DIR"));
          fprintf (outfile[node_idx], "%s. %s/install_profile\n", indent[node_idx], envvar ("TOOLS_DIR"));
          fprintf (outfile[node_idx], "%sexport LANG=%s\n", indent[node_idx], envvar ("LANG"));
        }
      } 
      else 
      {
        // Input redirection from /dev/echo enables that the ports reserved for remsh on both client and
        // server node get released immediately without any inactivity timeout period
        if (indent[node_idx][0] != 0) 
        {
          fprintf (outfile[node_idx], "\" < /dev/echo \n");
        }
        indent[node_idx] = "";
      }

      // Do parameter substitution
      strcpy(cmd2, cmd);
      expand_string (cmd2, "$BUILD_VERSION", BUILD_VERSION);
      expand_string (cmd2, "${BUILD_VERSION}", BUILD_VERSION);
      expand_string (cmd2, "$TARGET_NODE", curr_node);
      expand_string (cmd2, "${TARGET_NODE}", curr_node);
      curr_node_type = target_lookup (curr_node);      
      expand_string (cmd2, "$NODE_TYPE", curr_node_type);
      expand_string (cmd2, "${NODE_TYPE}", curr_node_type);

      fprintf(outfile[node_idx], "%s%s\n", indent[node_idx], cmd2);
      file_active[node_idx] = true;
    }
    curr_node = strtok(NULL, element_separator);

    whileiteration++;
  }
  printf ( "EXITING PROCESS PHASE\n" );
}

I've use code instrumentation to print out variables and other output on HP-UX and LINUX up until the while loop and they appear to be the same. Under certain circumstances, even when the while loop is ignored because the variable curr_node is null and the function is exited, where EXITING PROCESS PHASE is printed to stdout, the output is the same between HP-UX and LINUX. However, the return values between HP-UX and LINUX is completely different. What I can't figure out is why.

j0k
  • 22,600
  • 28
  • 79
  • 90
Justin
  • 742
  • 5
  • 17
  • 34
  • Maybe, this is connected with compile optimizations? – JustSomeGuy Jun 13 '12 at 17:46
  • I would look at the assembly output of both and see what the return register should hold towards the end or if it will be uninitialized in certain code paths. – Jis Ben Jun 13 '12 at 17:55
  • Is the code correct? Have you run it through vagrind? – Kerrek SB Jun 13 '12 at 17:57
  • I assume(?) that you may be also changing processors HP-UX ran on PA-RISC, 680x0, FOCUS, and Itanium processors. Whereas Linux is most commonly found on IA32 (i386 aka x86-32/64) and IA64 (Itanium) and several others including SPARC, MIPS, Alpha, and at least in the past PA-RISC and 68k. – mctylr Jun 13 '12 at 18:39
  • @mctylr: I guess that could be a possibility, although I wouldn't be able to say for sure since I'm not well versed in the design of CPUs. – Justin Jun 13 '12 at 18:43
  • My colleagues and I have discovered bugs that were never found on HP-UX systems using the gcc compiler, but were immediate segfaults on x86-32 running Linux using similar gcc versions. And I wouldn't be surprised if C++ compiler bugs were masked by processor differences (out of bound memory access I believe was one example). – mctylr Jun 13 '12 at 18:54
  • It's always a good idea to use `gcc`'s option `-Wall` to switch all warnings on. – alk Jun 14 '12 at 08:24
  • @Justin [Endianness](http://www.ibm.com/developerworks/aix/library/au-endianc/index.html) (big versus little, +odd balls), Data / address bus width (e.g. 32 or 64-bit), word/byte alignment (e.g. 4 bytes (32-bits vs. 8 bytes, 64-bit) of data structures. – mctylr Jun 18 '12 at 14:17

2 Answers2

6

You say there's a branch of your function "that does not explicitly state a return value." That's an error that the compiler is not required to diagnose for you. Behavior is undefined in that situation. That you've consistently gotten a negative result on one platform just means you're lucky (or unlucky, since different values wouldn't have exposed this error sooner otherwise).

Usually, there's a memory location or register designated for the return value to go. If the function doesn't store anything there, or if it's used the location for a temporary value but never written a final result there, then the caller will read that location and get whatever value happened to be there. The location for Linux return values happens to hold -72 in your case, whereas the location for HP-UX return values happens to hold zero. Make your intentions known and return a value explicitly in all code paths.

Whether the return-value locations consistently hold a particular value will often be affected by the actions of the function in question, as well as the actions of the functions it calls. Behavior of small, simple functions won't necessarily predict the behavior of larger functions.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I'm aware that this is very bad programming practice. I'm in the process of trying to figure out why the original coder didn't specify an explicit return value. There was another function that calls this function and if the return value is negative, it would end the application and present an error to the user. I've tested this undefined behaviour between linux and HP-UX with a simple integer function that has no return statement and the return value is the same between both linux and HP-UX, so I'm not sure if this is completely random. – Justin Jun 13 '12 at 18:12
  • @Justin No one said random. The program may even segfault (which is what happens on my Linux if I try this). Don't not return from a non-void function. – rubenvb Jun 13 '12 at 18:17
  • Just because your undefined behavior case seems behave consistently isn't meaningful. Undefined behavior means that *anything* can happen. The compiler is free to generate code the behaves differently on different days of the week. As for why the original programmer omitted the return statement, the most likely answer is that it was an accident. – jamesdlin Jun 13 '12 at 19:09
1

int values are most often returned in a register. If you don't actually return a value, that register will contain some random value remaining from previous computations.

If you reach the end of this function, you will have a return value from the last printf that will likely be passed on to the next level.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Well the return value is being obtained from somewhere I guess...it does not appear to be from the last printf i used... – Justin Jun 13 '12 at 18:26