C strings manipulation for D.Bane's Splash Screen/Text Logo

General help with the core C language of which eC is a superset, integrating eC with other languages such as C++ at the C level, using C libraries in eC, etc.
Post Reply
jerome
Site Admin
Posts: 608
Joined: Sat Jan 16, 2010 11:16 pm

C strings manipulation for D.Bane's Splash Screen/Text Logo

Post by jerome »

Hi D.Bane,

I'm back from the beach with the computer, so I can take a look at this :)
Sunburned despite spending the majority of the time inside reading Steve Jobs's biography :D

Okay, there are a number of issues here.

Scope

In C (and derived languages), there is the notion of scope within which variables exist.
Any local variable (defined inside a function) only exists within its scope, which is the compound block, delimited by { and }, the largest of which is the function's body.
What this means is that you cannot return a pointer to a local array and expect it to be valid memory outside the function. In this case, this applies to your char str[100]; and char str2[100].

So there are a few ways to work around this, to return a string from a a function:

1. You return it in a parameter, declared as char *, by doing a strcpy() into that char * parameter

2. You return newly allocated memory (CopyString), but it needs to be deleted by the caller

3. You return a pointer to memory allocated on the heap: a global, a static local variable, or a class member to which you have access, and is guaranteed to be alive. For example, textLogo. Especially for globals or statics, this can be dangerous with concurrency, if any kind of threading or recursion is going on, because any time the function is ran, the same variable is being used.

PrintBuf Usage

PrintBuf's first two arguments are the memory (buffer) in which to output the result, and its allocated length (the maximum number of bytes that will be written).
You were using strlen(str), which is wrong, as that tries to compute the length of the string, searching for a null ('\0') character in it. while you have not even initialized the str variable yet.
So the proper way is to use sizeof:
PrintBuf(str, sizeof(str), textLogo);

Substrings in C
Now you have:

Code: Select all

      if (returnMain == true) { rez = textLogo[0];/*str[0];*/ }
      else
      {
         int i;
         char str2[100];
 
         for(i = 1; i < strlen(str); i++)
         {
            str2[i-1] = str[i];
         }
         rez = str2;
      }
There are a few things that are wrong here... Firstly, you will notice a warning in the code as it is:

splash.ec:372:39: warning: incompatible expression this.textLogo[0] (byte); expected char *

Here you are mixing indirection levels, char and char *. textLogo[0] refers to the first character of the string, as opposed to the string itself (or in your case what you were hoping for, a new string that only contained the first character. char and char * are two fundamentally different data types, the former being a byte, the latter being a pointer.

In the else clause, you try to form a new string that starts at the 2nd character. In C, this is extremely easy to do (much easier than a substring at the start of the string as above!) with pointer arithmetic, without having to copy anything, you simply do 'textLogo + 1'. Because C strings works with null-termination, i.e. the string ends when a null character is found, this is possible. A few things to note about your original else clause, it would need to go one character further to copy said null character: i < strlen(str) + 1. Also, because each time you invoke strlen() it has to search the entire string, a better way would be:
for(i = 1; str; i++)

This will loop until str is the null character itself, at the end of the string. (But you would still need to add the null character at the end: str2 = 0;
Another OK approach would be to compute the strlen() before the for, e.g. int len = strlen(str); and then use i < len + 1

Again in this else clause, you made the scope mistake of assigning str2, which is only valid inside the else, to a variable declared outside (rez).

So here is some code that does work:

Using a static local array:

Code: Select all

   String TextLogoPrepare(bool returnMain)
   {
      if(returnMain)
      {
         static char str[2];
         str[0] = textLogo[0];
         str[1] = 0;
         return str;
      }
      else
         return textLogo + 1;
   }
Returning newly allocated memory (caller must delete):

Code: Select all

   String TextLogoPrepare(bool returnMain)
   {
      if(returnMain)
         return PrintString((char)textLogo[0]); // (char) is needed here, otherwise E gets printed as 69 ( Bug http://ecere.com/mantis/view.php?id=681 )
      else
         return PrintString(textLogo+1);
   } 
Same as static example, but using PrintBuf instead:

Code: Select all

   String TextLogoPrepare(bool returnMain)
   {
      if(returnMain)
      {
         static char str[2];
         PrintBuf(str, sizeof(str), (char)textLogo[0]);
         return str;
      }
      else
         return textLogo + 1;
   }
Returning newly allocated memory with CopyString (must delete):

Code: Select all

   String TextLogoPrepare(bool returnMain)
   {
      char * rez;
      char str[100];
      if(returnMain)
      {
         str[0] = textLogo[0];
         str[1] = 0;
         rez = str;
      }
      else
         rez = textLogo + 1;
      return CopyString(rez);
   }
Taking a buffer as a parameter:

Code: Select all

   char * TextLogoPrepare(bool returnMain, char * output)
   {
      if(returnMain)
      {
         output[0] = textLogo[0];
         output[1] = 0;
      }
      else
         strcpy(output, textLogo + 1);
      return output;
   }
To use this last one, in OnRedraw you change 'char * s;' to 'char s[100];'

And add s as an argument to calls to TextLogoPrepare, e.g.:

TextLogoPrepare(false, s);

For such a scenario, I would normally prefer the last solution.

Class member/property initialization
Another thing I've noticed, at the top of the program you were trying to assign initial values to the class properties, e.g.:

String textLogo = "ECERE";

eC does not yet support this syntax to both declare a class member and initialize it together at the same time, although I hope to make it work in the future. It can be done however, as you had it commented out, in two parts:

String textLogo; textLogo = "ECERE";

The problem with this, is that within a class, data members are resolved before properties.
Because textLogo is both a data member and a property of the class, here the data member takes precedence, and so "ECERE" gets assigned to the data member textLogo. That would also obviously be what happens if we had the String textLogo = "ECERE"; syntax, because that is the member you are declaring!!

Now the problem with this here, is that when using the property, you assign a copy of the string, and delete it upon destruction. Deleting a string literal constant will crash.
( I just saw your "Works only if there is no default value set for it" comments )
There are two ways to work around this:

1. String textLogo; textLogo = CopyString("ECERE"); // Since you know you have to make a copy, you can do it on the initialization

2. String textLogo; property::textLogo = "ECERE"; // This forces the 'property' textLogo to be invoked, instead of being a simple data member assignment. The textLogo property will call CopyString and assign the new string to the textLogo data member as usual

Conclusion

C strings are based and presented as their low level pointer to characters implementation, and it can be quite a challenge to grasp them. Sadly, eC does not yet do enough to simplify this and make life easy for non-C programmers. We hope to have a nice String class in the future that will change this. Until then, I hope you can find consolation in the fact that you are gaining C and system programming skills :D Those same operations that deal with characters in strings apply to dealing with memory for low-level system programming.

Cheers,

Jerome
D.Bane
Posts: 78
Joined: Thu Jul 29, 2010 2:51 pm

Re: C strings manipulation for D.Bane's Splash Screen/Text L

Post by D.Bane »

Hi Jerome.

I honestly do not know to laugh or cry right now... :) Doing "txtLogo + 1" is just too easy of an answer and there I was spilling (nonworking) code everywhere..

Graphical response :)
8-) :arrow: :oops: :arrow: :lol: :arrow: :ugeek: :arrow: :D

Thank you for the answer. I really thought that you can do local string and assign it to a global one without any problems, so this is great to read.

Regarding default values I will add the property:: part to them, as I think it is nicer to read from the code.

Hmm, PrintBuf(), I usually put sizeof() there, do not know why I haven't this time, but it is nice to hear why I should put sizeof() there in the first place as I did not know that.

Well, until the string class does not come to Ecere I think I'll just try how what works and ask here if I have any trouble. This has proven already as a great source of knowledge regarding Ecere and C alike.

Thank you very much Jerome for explanation and I hope that it was not a problem for you to write this detailed answer since you got sunburned. I heard that his biography is a great book to read so I hope you had a great time reading it, and/or got inspired for more in Ec :)

All the best,
D.Bane.
No army is to big, if the heart is beating strong.
There's no bad luck, it's just easier to blame someone else.
jerome
Site Admin
Posts: 608
Joined: Sat Jan 16, 2010 11:16 pm

Re: C strings manipulation for D.Bane's Splash Screen/Text L

Post by jerome »

Just to clarify something:
I really thought that you can do local string and assign it to a global one without any problems
This would be ok, despite s1 and s2 being "local" strings:

Code: Select all

String globalString1, globalString2;
 
void DoSomething()
{
   String s1 = "Hello"; // "Hello" is a string literal constant, so its memory is valid outside the function
   String s2 = CopyString("Something"); // The memory is allocated on the heap, so it is valid outside the function
 
   globalString1 = s1;
   globalString2 = s2;
}
A local char 'array', however, is only alive within its scope, because the array's (the string's) memory is on the stack.

Steve Jobs's bio: very inspiring, indeed! I could not wait to come back to the computer and make Ecere/eC move forward!! :D Now I'm enjoying another week of focus on Ecere, I will be doing some improvements to the compiler among other things. Then I have one last week of vacation which we will be spending in Kyoto, visiting Japanese Zen gardens, just like Steve Jobs used to do! This is partly where he got his fondness for simplicity and minimalism from (e.g. one button mouse! hehe). I highly value simplicity and minimalism as well, especially when it comes to the amount and elegance of code necessary to accomplish something :ugeek:

When I get back home it will be more challenging to find time to work on Ecere, but I do have high hopes for Ecere accomplishments in 2012!

Cheers,

Jerome
D.Bane
Posts: 78
Joined: Thu Jul 29, 2010 2:51 pm

Re: C strings manipulation for D.Bane's Splash Screen/Text L

Post by D.Bane »

Hi Jerome.

Thank you for the explanation I will remember this.

OK so it is true that it is a great book, will see if it comes to these parts :)

Zen Gardens - That is cool I always liked that so I hope that you will have great time there.

Oh yes, and minimalism, I believe that is the word that one could use to describe Ecere, when they want to say how easy it is to create something that works over so many different platforms and requires about 10 lines of code (currently speaking about creating plain window), but of course anything else is short in code as well. I compared the code of few VB6 projects and of eC and it is something to see to believe as Ecere is always faster and code can be even smaller.

All the best and let the Ecere get a lot better :)

D.Bane.
No army is to big, if the heart is beating strong.
There's no bad luck, it's just easier to blame someone else.
Post Reply