You are here:

C++/C++ passing variables

Advertisement


Question
void main(void)
{
  int r;
  int b;
  sub(1,r,b);
  //r will = 0 and not 2 like it is supposed to
  //b will = 0 and not 2 like it is supposed to

}
void sub(int Var1,int Var2,int var3)
{
   Var2 = Var1 + 1;
   Var3 = var1 + 1;
}
this is basically what I have, I am trying to input variable into the subprogram and then pass the results back to the host program. but it does not work. the variables do not get passed back. what am I doing wrong? this may be a simple problem and that may be why my search quarries don't give me a result. I am used to basic and assembler if this explains anything :)

Answer
Depends on the variant of BASIC. If you are using a newer variant such as Visual Basic then you might have come across qualifiers for function / procedure parameters such as ByVal and ByRef.

No matter what language you are using you have two main ways to pass values to functions, procedures, subroutines etc.: By value, or by reference.

Passing by value means that the function, procedure or subroutine gets a _copy_ of the argument value to work on, which typically lasts for the duration of the call, like other stack-based local objects (variables). Because the function call receives a copy original argument object can _never_ be modified during a call. Pass by value also allows literal values to be passed - the 'copy' is an object that has the value of the literal value. Such parameters are sometimes called input or in parameters as they can only be passed into a function.

Pass by reference on the other hand means that a reference to an argument object is passed into a function. Because the function call works on a reference, modifications to the referred to object modify the original argument object. You cannot pass references to literals (at least in C and C++), as they have no real way they can be referred to - what would it mean to add one to the literal 1? Would 1 now be 2? Pass by reference parameters may be termed output or out or input, output or in, out as they allow data to be passed out of a call, and depending on the semantics may also be passed into a call.

In assembler to pass by value you would copy (move, load etc.) the value to be passed to a function, for example you might copy the value in some memory location into a register. To get the effect of pass by reference you would copy the value of the _address_ of the argument to be passed - for example moving the argument's (start) address into a register.

In C and C++ all parameters are passed by value. In fact for C the situation mimics the assembler case (C is sometime referred to as a portable assembler!). To get the effect of pass by reference you have to pass the values of pointers to variables. Pointers in C and C++ are analogous to memory addresses (in fact in most cases for C this is exactly what they are). To declare a pointer type you use the * after the type name. In fact, strictly speaking it goes before object (variable) name thus:

   int r;     // r is of type int
   int * p;   // p is of type pointer to int

To access what a pointer is pointing at we have to dereference the pointer:

   int q = *p;

We can obtain a pointer to an object by using the unary & operator thus:

   // &r takes the address of r.
   // Here it is assigned to p so p points to r.
   int * p = &r;

Note:

1/ that also in C and C++ local automatic (stack) objects (variables) of the built in types are _not_ initialised to any specific value - and therefore may end up with a value determined by what rubbish was on the stack to start with - remember the memory used by the stack is re-used as functions are called and returned from.

2/ that void main(void) is _not_ a valid C or C++ signature for main - it is a Microsoft 'extension' (perversion). Use:

   int main()
   {
   // ...
   }

Or

   int main( int argv, char * argv[] )
   {
   // ...
   }


3/ in C++ a function having no arguments does not need to be specified using (void) - we can use () instead. Also the return type _must_ be given unlike C where no specified return type - not even void - implies an int return type.

Thus you main function should look like this:

   int main()
   {
      int r;
      int b;

   // ...

   }

Oh and of course r and b are un-initialised, and thus must be considered to contain random rubbish. Likewise in my examples above, assuming that p were a local pointer object then it also was originally defined as being un-initialised and so must also be considered as containing rubbish - very bad for a pointer as dereferencing it means that some random and probably invalid address is dereferenced which if we are lucky gives an immediate operating system / hardware exception or trap, crashing the program. If we are unlucky then the address will be a random _valid_ address for the program and thus no crash will occur and data will be read from or written to this random location - which probably will only cause an error later on and is therefore much less easy to locate and fix!

As far as I can tell in _this_ case, which seems a little contrived the Var2 and Var3 parameter values are only passed out of the function, so leaving them uninitialized is OK. However if they needed to be input and output then r and b would need to be initialised to some sensible values before being passed into the function. (if for example the body of sub read:

       ++Var2;
       ++Var3;

This modification would pre-increment Var2 and Var3 each by 1, thus the original values of Var1 and Var2 are relevant here, so they should be some known values:

   int main( void )
   {
      int r = 0;
      int b = 0;

   // ...

   }

Or for C++ only:

   int main()
   {
      int r(0);
      int b(0);

   // ...

   }

The general advise is that local (stack) objects (variables) should _always_ be initialised to a known value. It causes less problems in the long run. If you forget when it is important you have a bug, quite possibly an intermittent bug which means of course that it does not show up in your testing but does for that high profile large account customer! If you do it and its not really required you might waste a usually totally insignificant amount of time.

From what I have said already you should now be thinking that the sub Var1 parameter should be passed by value and is therefore OK as it is, but that Var2 and Var3 need to be passed by reference, and thus need to be passed by pointer for C, which will also work with C++ (I have more to say on the situation for C++ below). This would revise sub as follows:

   void sub(int Var1, int  * pointerToVar2, int * pointerToVar3)
   {
       *pointerToVar2 = Var1 + 1;
       *pointerToVar3 = Var1 + 1;
   }

It also means we have to modify the call point of sub in main, as Var2 and Var3 paraemters are now pointers to int just passing r and b is not correct (type int is not equal to type int *) we have to pass the addresses of r and b rather than copies of r and b:

   int main()
   {
      int r;
      int b;
      sub(1, &r, &b);
   }

Now there is a feature of pointers that has to be catered for when using they they may be the null pointer value. It is illegal to dereference a pointer whose value is the null pointer value, again the most likely (hopefully) result of doing so is that the program will crash. The null pointer value is useful for initialising pointer objects to before it is known what they point to. This is because we can write code that explicitly checks for null pointers, and indeed I advise that sub performs these checks to be safe:


   void sub(int Var1, int  * pointerToVar2, int * pointerToVar3)
   {
       if ( ! pointerToVar2 || ! pointerToVar3)
       {
       // take some error handling action e.g. return an error value
       // (implying that sub returns a value and not void)
       }
       else
       {
         *pointerToVar2 = Var1 + 1;
         *pointerToVar3 = Var1 + 1;
       }
   }

Note that I have changed the names of Var2 and Var3 to make it clear they are pointers and thus need to be dereferenced before the value pointed to can be accessed.

I test to see if either of the pointer parameters are null using the often used assumption that a null pointer is zero and therefore equivalent to a Boolean logic false value. This is true in C++ _but_ may not be true in C on platforms where a null pointer has a bit-pattern other than all zeros some older processor architectures did have such a value (Prime mini computers for one). In C++ it is not a problem because the language definition states that whatever the actual value of a null pointer, such a value is always convertible to a zero integer value. Thus in C++ we can use:

       if ( 0==pointerToVar2 || 0==pointerToVar3 )

However in C we have to use the NULL macro definition (include <stddef.h>):

       if ( NULL==pointerToVar2 || NULL==pointerToVar3 )

OK so that is how we use pointers to pass parameters to functions by reference. In C++ we have another option. C++ supports reference types. These can be considered like pointer that do not have to be dereferenced but _must_ always be initialised with the object they are referencing (or an existing reference to that object).

The syntax for declaring a reference is similar to that for a pointer except that & is used instead of *, thus:

   int b;       // b is type int
   int & r(b);  // r is type reference to int initialised referring to b

Or if your compiler complains about the C++ initialisation syntax (some MS VC++ compilers get confused with this syntax for initialising pointers and references):

   int & r = b;  

To use the reference we do not need to dereference it:

   b = 3;
   r += 4; // b == 7.

   assert( r == b ); // include <casssert>

I use the assert macro to assert that both the object b and the reference to b, r evaluate to the same value. Note that assert is a macro originally in the C standard library and inherited by the C++ standard library. In C include <assert.h>. The macro writes information to the standard error stream and aborts the program if the assertion fails. However it only does this if NDEBUG is _not_ defined. NDEBUG is usually defined for fully optimised release builds. In such cases the assert macro expands to remove the contents of the assertion that is the assertion only occurs for debug builds. Thus _never_ put code that does real work in an assert!

We can use reference types as function parameter types, thus:

   // Var2 and Var3 are passed as references to int
   void sub(int Var1, int  & Var2, int & Var3)
   {
       Var2 = Var1 + 1;
       Var3 = var1 + 1;
   }

Note that this is much closer to your original function. Also as references are _always_ initialised to refer to something and there is no such thing as a null reference there is no need to check for them!

Your original main should execute as expected (with the fixed signature for main!):

   int main( void )
   {
      int r = 0;
      int b = 0;

      sub(1, r, b);
   }

Hope this is of use. Have fun.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.