Link to home
Start Free TrialLog in
Avatar of simone222
simone222

asked on

return a char* from a calss

hi
i have a class with a function. i should return a char* but i've heard that it's not proper and can give some problems. what would be the best way to do that?
there is something special with char **test but i don't know how to work with that exactly. annother solution would be to give the adress to the function (one more func. param.)
what is the most proper way, what do u think.
can u help me with char ** ?

thanx alot
Avatar of nietod
nietod

There is nothing inheriently wrong with returning a "char  *" from a function (whthor that is a member function or not).  However, there are many issues to consider when you do so and often--not always--these issues make this an unwise thing to do.

continues
The "problem" with returning a character pointer to a string from a function is that you need to insure that the string that the pointer points to still exists when the caller uses the pointer.  For example, if you had code like

char *somefunction()
{
   char AnArray[100];
   char *Ptr = AnArray;

   strcpy(AnArray,"some text.");
   return Ptr;  
}

this would be a terrible mistake.  The pointer returned points to a local varaible (AnArray)  This array will be destroyed when the function ends.  So the caller will receive a pointer to an array that no longer exists.  If the caller tries to use that pointer, the behavior will be unpredictable.  it might be fine at times, it might cause crashes at times.  its certainly an error.

continues
Now there are a few ways around this.

You can dynamically allocate the array that you return the pointer to, like

char *somefunction()
{
   char *Ptr = new char[100];

   strcpy(Ptr,"some text.");
   return Ptr;  
}

The "problem" with this is that the caller must now remember to delete the array when it is not longer needed.  It would be easy for the caller to forget this and thus introduce a memory leak.  However, despite this weakness, this is still a good solution.


Another solution is to use memory passed by the caller, like

char *somefunction(char *Ptr)
{
   strcpy(Ptr,"some text.");
   return Ptr;  
}

In this case, the caller suplies the memory to be filled by the function.  The procedure can still return the pointer, although this really is not as necessary as the caller obviously has the value already (although this is a standard practice).  the advantage to this is the caller is ultimately reposible for the entire lifetime of the memory and thus less likely to cause a memory leak.  The dissadvantage is that the function being called has no control over the memory.  It cannot insure that the memory is sufficiently large enough for its needs or even that the pointer is even valid.  However, this is still a good solution.

A third option is to use static memory.  This is a very limited solution, but a very safe one where applicable.  For example

char *somefunction()
{
   static char AnArray[100];
   char *Ptr = AnArray;

   strcpy(Ptr,"some text.");
   return Ptr;  
}

this procedure returns a pointer to a local static array of characters.  This works because the static array is not destroyed when the procedure ends.  It is destroyed only when the program ends.  So the caller can safely use the pointer returned even after the procedure has ended.  The dissadvantage to this is that there is only one copy of this memory.  So if the procedure is called a 2nd time, it will have to "reuse" this memory and this might force it to corrupt value that was previously in the array and this coudl cause a problem if that value is still being used elsewhere in the program.  This is a good solution, if you can insure that the value will be used only until the next time the procedure is called.  otherwise it is a terrible solution.

However, there is a far better approach to all of this.  String objects.

continues

In C NUL terminated character arrays are almost the only way to represent strings and passing around char * pointers is nearly the only way to pass around strings.  Certainly these are the easiest ways to do so, but they are also very problematic ways.  When you pass a char * to a procedure, you are not passing a new copy of the string, you are passing a new copy of the pointer and that pointe points back to the same character array as the original pointer.  This means that the caller and the called function must share the same character array.  this leads to all sorts of potentials for problems.  All of these problems can be cirumvented when necessary, but it takes effort to do so and it takes care to detect when there will be problems.  This leads to lots of bugs.  furthermore strings like this can't be compared using the relation operators, like < > == etc.  You must use strcmp().  You also can't copy them use the = opperator, you must use strcpy().  You can't concatenate them with the + operator, you must use strcat.  And worst of all, these strings are limited in length by the size of the array.  If you attempt to store a string that is too long in the array, it will overflow the array and cause bugs and crashes.

For these reasons, C-style strings (NUL termianted character arrays) are almost always bad idea and should be avoide except when absolutely necessary.  in C they were necessary.  In C++ they are not.

C++ provides string objects.  These are objects that record strings.  However they are much better behaved than NUL termianted character arrays.  They can be passed to a function by value and returned by value.  They can be compared using the relational operators and can be assigned using =.  For example.

string SomeFunction()
{
   string S = "ABC";
   return S;  // No problem;
}

also

  string S1 = "ABC";
  string S2 = "DEF";
  string S3 = S1 + S2;

  if (S1 != S2)
     ;

etc.

You can write yoru own string class, but the C++ standard library provides a string class called "string".  it is defined in <string> and is a very good class.  it uses reference counting which is a technique that makes the string very fast and efficient-often faster than C style strings.
>> can u help me with char ** ?
I didn't mention this technique.  its similar to the first solution I mentioned.  It has the same weakness--that the caller must remember to free the memory.  

In this case the caller passes a pointer or reference to a pointer that it has allocated.  The called procedure then allcoates memory and changes the calling procedure's pointer so that that pointer points to the memory allocated.  Like

void Caller()
{
   char *StrPtr;  // Allocate pointer to string.
   Called(&StrPtr); // Pass pointer to that pointer.
}

void Called(char **Ptr)
{
   char *P = new char[100];
   *Ptr = P; // Set caller's pointer to point to this memory.
}
Avatar of Axter
FYI:
One way to make sure that the allocated memory gets deleted, is to use something like an auto_ptr class.
Example:
class auto_char_ptr
{
public:
     auto_char_ptr(char *ptr):m_ptr(ptr){}
     ~auto_char_ptr()
     {
          delete []m_ptr;
     }
     operator char* const()
     {
          return m_ptr;
     }
     char * const m_ptr;
};

char* SomeFunction1(void)
{
     char * data = new char[64];
     strcpy(data,"Hello There");
     return data;
}

void SomeFunction2(void)
{
     auto_char_ptr Hello = SomeFunction1();
     cout << (char*)Hello << endl;
     cout << Hello.m_ptr << endl;
}

In the above function when the auto_char_ptr variable (Hello) looses is scope, it automatically deletes the pointer.
This method insures that you don't forget to delete the pointer.  There by avoiding posibility of memory leaks.
Answered as above.

If you have additional questions, let me know.
Avatar of simone222

ASKER

let me study all the suggestions first, i WILL give u points if u're deserving to receive this points...
thanx
>> but the C++ standard library provides a string class called "string".
 
the problem is, i need to return 8bit variables and not ascii characters. i think there is no other way if the length is variable...

nietod:
i had a look on all your suggestions and i thank you very much for your help, nevertheless i please you not to answer the question so alot of other experts have access to give their comments to this thread, this would help everyone. and if u gave a good answer, i will grade you with points...!

thank you

?simone?
i did following:

char*  __fastcall TComPort::RecChar(int Value)
{
  static char InBuff[100];
  char *Ptr = InBuff;
  DWORD dwBytesRead;

  ReadFile(hComm, Ptr, Value, &dwBytesRead, NULL);
  if(dwBytesRead)
    {
    InBuff[dwBytesRead] = 0; // NULL Terminate the String
    return Ptr;
    }
}



and i received this Error:

Project Project1.exe raised exception class EAccessViolation with message 'Access violation at address 00404198. Read od address 00000001'. Process stopped. Use Step or Run to continue.
>> the problem is, i need to return 8bit
>> variables and not ascii characters. i
>> think there is no other
>> way if the length is variable...
But you currently are not returning an 8 bit variable--a char.  You are returning a pointer which is almost certainly much larger than 8 bits.  However that pointer can be used to "find" the 8 bit variables you want to provide to the caller.  This is true of a string object as well.  it is not an 8 bit value, but it can be used to help the caller obtain those values.

>> nevertheless i please you not to answer the
>> question so alot of other experts have access
>> to give their comments to this thread,
You should state this initally in your question.   The rules clearly state that experts can answer questions when they have an original and correct answr.  If I had any idea at the start that you did not want an answer submitted I would not have.  In fact I would have not provided any answer/comments at all.  as the time stamps show, I spent nearly 30 minutes writting an answer for you.  Write an answer that is completely correct and extremely comprehensive.  I would never have spent that time if I thought that anyone else would be getting the points!


Now you are asking a 2nd question.  This is a completely unrelated question. And I don't even have the right to answer it?



The ReadFile() function works like the 2nd example I posted.  The caller must suply the memory to be used by the function.  i.e the caller must pass a pointer to the location where the data read will be stored.  You are passing an uninitialized pointer.  You need to pass a pointer that point to something--to the location you want to store the data in.
dear nietod,
i really respect the work i did and still do..!
as i told, I WILL GRADE you, if the solution u posted is useable for me. i think this forum is here for learning and benefit from others. and in my judgement it is okay if other experts can give some comments to this thread, too...
if i do this:

char*  __fastcall TComPort::RecChar(int Value, unsigned char *InBuff)
{
  DWORD dwBytesRead;

  ReadFile(hComm, InBuff, Value, &dwBytesRead, NULL);
  if(dwBytesRead)
    {
    InBuff[dwBytesRead] = 0; // NULL Terminate the String
    return InBuff;
    }
  return NULL; // no data read;failure ?

}

occurs also an exception....cause it wants to read from address 00000000

if u don't wanna help me anymore, i will give you the points...
>>char*  __fastcall TComPort::RecChar(int Value, unsigned
>>char *InBuff)
If you use this method, then you should not use the return char* on it, because that would be redundent.
Try this:
bool  __fastcall TComPort::RecChar(int Value, unsigned char *InBuff)
{
 DWORD dwBytesRead;

 ReadFile(hComm, InBuff, Value, &dwBytesRead, NULL);
 if(dwBytesRead)
   {
   InBuff[dwBytesRead] = 0; // NULL Terminate the String
   return true;
   }
 return false; // no data read;failure ?
}
Remember with the above method, that the calling function has to provide the space for it.

somefunction()
{
char data[64];
if (TComPort::RecChar(sizeof(data),data))
{
//Ok, it was good, so do something with it
}
}
>>occurs also an exception....cause it wants to read from
>>address 00000000
Your code should not make this variable equal to NULL if you're passing a variable from the calling function.

If you still want to use the char* return type function, you could make your code look more like the following:

char*  __fastcall TComPort::RecChar(int Value)
{
 DWORD dwBytesRead;
 char *Buffer = new char[Value + 1];//The +1 is for the terminator
 ReadFile(hComm, Buffer, Value, &dwBytesRead, NULL);
 if(dwBytesRead)
   {
   Buffer[dwBytesRead] = 0; // NULL Terminate the String
   return Buffer;
   }
 delete Buffer;
 return NULL; // no data read;failure ?
}

With the above function, the calling function would have to do a NULL test before it did anything with it.
Example:

somefunction(void)
{
char * MyData = TComPort::RecChar(64);
if (MyData != NULL)
{
//Ok, do somethis with it
}
//Then don't forget to delete it
delete MyData;
}
Correction on the bool return example:

bool  __fastcall TComPort::RecChar(int Value, unsigned char *InBuff)
{
DWORD dwBytesRead;

ReadFile(hComm, InBuff, Value-1, &dwBytesRead, NULL);
if(dwBytesRead)
  {
  InBuff[dwBytesRead] = 0; // NULL Terminate the String
  return true;
  }
return false; // no data read;failure ?
}

I added a '-1' to the ReadFile next to the Value, because you need to save space for the NULL terminator.
I don't recommend that you use the char* return example, because it's an unsafe practice, and it would be easy to forget to delete the variable.  If you do use it, I recommend that you use the previous auto_char_ptr class that I posted.  It would delete the variable for you automatically.
Example:

somefunction(void)
{
   auto_char_ptr  MyData = TComPort::RecChar(64);
   if (MyData.m_ptr != NULL)
   {
      //Ok, do somethis with it
   }
   //You don't have to delete it
   //MyData will delete itself when this
   //function is done.
}
simone222, you are permitted only one question per EE question.  I answered your original question and you rejected it without providing a reason.  if there is something you cannot understand with it.  Let me know so I can help you.

You are now asking a 2nd completely unrelated question. That is against the eE rules.  I aided you in that as well.   but what is going to happen here?  You will probably awayrd points to someone who answers this question, or your 3rd or 4th question or so one.  That is not fair and that is why EE forbids this.


*************************

the code

 DWORD dwBytesRead;

  ReadFile(hComm, InBuff, Value, &dwBytesRead, NULL);
  if(dwBytesRead)
  {
      InBuff[dwBytesRead] = 0; // NULL Terminate the String

doesn't do what you want it to do, it probably doesn't do what you think it does.

InBuff must be an array of some sort and with some specific length.  This code sets the item in the array at position dwButesRead to 0.  It DOES NOT resize the array.  You need to do something more like

 DWORD dwBytesRead;
 char *InBuff;

  ReadFile(hComm, InBuff, Value, &dwBytesRead, NULL);
  if(dwBytesRead)
  {
     InBuff = new char[dwBytesRead];

this creates an array dynamcially that is the required length.  This array must be deleted with delete [] later.

Note that all of Axter's examples have this mistake too.  Be very careful about what expert you listen to.
ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
huah, **** forgot to initialize it!
thanx....