Author Topic: Pointers are the bane of my existance  (Read 5585 times)

halkun

  • Global moderator
  • *
  • Posts: 2097
  • NicoNico :)
    • View Profile
    • Q-Gears Homepage
Pointers are the bane of my existance
« on: 2006-09-17 23:23:36 »
Ok, I could never, *EVER* get pointers in C. I mean, I've tried, but I just get so borked up on them.

Here's the probelm, I'm trying to access a global pointer using TCL. Now TCL has a function that will allow you to mirror a variable in your C code with an external one in the TCL script.

You need to feed the function a "char *" that points to your string...

Ok, I've got that

So first I made a string and then I tried to feed it to the function

Code: [Select]
char name[8];
Tcl_LinkVar(interp, "creator", &name, TCL_LINK_STRING);

Nope that didn't work says it's the wrong type? It's a char, is it not? Ok, fine, I'll use a cast.

Code: [Select]
char name[8];
Tcl_LinkVar(interp, "creator", (char *) &name, TCL_LINK_STRING);

now it just segfaults. What on earth am I doing wrong?

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Re: Pointers are the bane of my existance
« Reply #1 on: 2006-09-17 23:29:01 »
The problem is that you pass the address of an array to your function, but the array itself is already treated as a pointer.
So, you have 2 possibilities to solve your problem:
You can either cast the array itself to a pointer, or you pass the address of the first element of your array.

Possiblity #1:
Code: [Select]
char name[8];
Tcl_LinkVar(interp, "creator", (char*) name, TCL_LINK_STRING);

Possibility #2:
Code: [Select]
char name[8];
Tcl_LinkVar(interp, "creator", &name[0], TCL_LINK_STRING);

 - Alhexx

 - Edit -
Okay, I've decided to explain a bit more why your code did not compile correctly:

Quote from: halkun
Nope that didn't work says it's the wrong type? It's a char, is it not?
No, it's not. As I said, you can treat an array as a pointer. The reason why it says that it's the wrong type is simple:
The functions expects a char*, but you gave it a char** (i.e. a pointer to a pointer).

Quote from: halkun
now it just segfaults.
The reason why it did compile is simple: you violenty casted a char** to a char*.
But instead of casting the array to char*, you have casted the address of the array. The function then tried to overwrite the buffer with "creator", but since the size of a pointer is 32 Bit (at least on a 32 Bit OS) and you tried to write 64 Bit, it caused a segfault.

I hope you understand what I mean.
« Last Edit: 2006-09-17 23:55:49 by Alhexx »

halkun

  • Global moderator
  • *
  • Posts: 2097
  • NicoNico :)
    • View Profile
    • Q-Gears Homepage
Re: Pointers are the bane of my existance
« Reply #2 on: 2006-09-17 23:55:28 »
It still segaults.

This is the man page for the function, maybe you can make heads or tails out of it...

« Last Edit: 2006-09-17 23:58:08 by halkun »

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Re: Pointers are the bane of my existance
« Reply #3 on: 2006-09-17 23:58:37 »
Maybe the problem is that you did not initialize the array.
Try this one:
Code: [Select]
char name[8] = {0};
Tcl_LinkVar(interp, "creator", (char*) name, TCL_LINK_STRING);

 - Alhexx

 - Edit -
Oh, well, now I think I know...
Quote from: Man Page
char    *varName    (in)
Name of global variable. Must be in writable memory: Tcl may make temporary modifications to it while parsing the variable name.
The string "creator" is read-only. You should do something like this:
Code: [Select]
char varName[16] = {0};
char name[8] = {0};
memcpy(varName, "creator", 8);
Tcl_LinkVar(interp, (char*) varName, (char*) name, TCL_LINK_STRING);

 - Edit #2 -
spelling...
« Last Edit: 2006-09-18 00:16:55 by Alhexx »

Cyberman

  • *
  • Posts: 1572
    • View Profile
Re: Pointers are the bane of my existance
« Reply #4 on: 2006-09-18 00:23:08 »
A few coding suggestions perhaps?
1 if you define an array USE a #define for it's length... that way if you NEED it's length you can find and change it's length easily.
2 you may want to define all globally accessed variables in one module and have an initialization function set for TCL called in a correct place.
3 "creator" BAD BAD halkun this is NOT the way to do it read this statement carefully :D
Code: [Select]
Tcl_LinkVar(interp, varName, addr, type)

char    *varName    (in)
Name of global variable. Must be in writable memory: Tcl may make temporary modifications to it while parsing the variable name.
not WRITEABLE although "creator" is cute it's not writable memory you should store the name in a variable then pass the variable. This is likely where your segfault is coming from
Code: [Select]
char name[8];
char Creatorname[64] = { "creator" };

void TCLInitializationFunction(void)
{
   Tcl_LinkVar(interp, Creatorname, name, TCL_LINK_STRING);
}

that may work

mirex

  • *
  • Posts: 1645
    • View Profile
    • http://mirex.mypage.sk
Re: Pointers are the bane of my existance
« Reply #5 on: 2006-09-18 09:30:33 »
I'll give you some small hints as well Halkun.

Dunno if it will work for you, but mine C++ compiler can compile this:

char name[] = "Something";

that means it does not need the array size definition if you define the string right after it. Makes it nicer. :)

Also array can be translated as a pointer to the array members ... that means that char name[ 8 ]; - name can be used as char* ... int data[ 8 ];  data is int* as well. But in some cases it won't work.

for your information all the char[] char* strings in C are required to have a zero byte ( or '\0' char ) on their end. That's what marks the end of string. If you create a string like   char  name[ 8 ];  its contents are undefined, it can contain 8x "A" which means that it is not ended anywhere - that means any operation on it would end up randomly, probably reading / writing in some invalid memory. You should allways fill up your variables, you can't expect them to reset automatically. Good way is to:

char name[ 8 ] = { 0 };
or
char name[ 8 ];
name[0] = 0;  <- sets the first byte to zero, which means start of string is also its end.

This means another complication which some people overlook. If you have char name[ 8 ]; and you use it as a string you can't fill it with 8 characters. Because there has to be space for the zero ending character. So it can contain max 7 chars + 1 zero character.
strcpy( name, "12345678" ); will copy 8 characters into the 'name' variable, and it will write the 9th zero character outside - somewhere to undefined memory. It can work, right, but it also can overwrite some important variable memory.

C++ overcomes all these problems with dynamic std::string<> which takes care about the space the string needs ... but it also brings another problems ;)


About the function ... I was reading the man page and I found:

TCL_LINK_STRING
    The C variable is of type char *. If its value is not null then it must be a pointer to a string allocated with Tcl_Alloc. Whenever the Tcl variable is modified the current C string will be freed and new memory will be allocated to hold a copy of the variable's new value. If the C variable contains a null pointer then the Tcl variable will read as ``NULL''.

which brings me to the conclusion that you have to alloc the variable in a special way as it could be changed / resized when some other string changes. So maybe you should use it this way:

Code: [Select]
Tcl_Interp  inter;  // returned error code
char varname[] = "Variable"; // variable name

char* newstring = Tcl_Alloc( default_size_of_your_string );
strcpy( newstring, "hello" );

Tcl_LinkVar( &inter, varname, newstring, TCL_LINK_STRING );
// check for return code
if ( inter == something )
 do something.

// and maybe somewhere at the end you should free the string
Tcl_Free( newstring );

Well this is just my guess from the man page ... dunno if its correct because I don't know the lib ... also some thing puzzle me ... why should be the varname changed and how ? Maybe it should be specially allocated as well ? Maybe if thou do not want to define the string you don't have to alloc it and you can use NULL parameter instead Tcl_LinkVar( &inter, varname, NULL, TCL_LINK_STRING ); ?

It'd be better if you would see for some examples used with this function to see the proper usage.

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Re: Pointers are the bane of my existance
« Reply #6 on: 2006-09-20 21:31:05 »
Well, it seems like halkun is not the only one on this planet who has problems with stuff like this... :P

I recently got a book from eBay named Programming Dynamic Character Animation by David B. Paull.
The author wrote a basic 3d rendering engine and included the source code of his engine on his book's CD.
I just opened the source files, compiled them and watched the engine crash at startup. So I started the debugger and found something really nasty - nearly 100% the same reason why the segfault appeared in that function we discussed earlier in this topic.

I'm going to go through the debugger backwards, i.e. I'll start at the point where the crash happens back to the reason of the crash.

The function that causes the crash is sprintf. It was called this way:
Code: [Select]
sprintf(pFileName,"%s",pName);(Note: pName is a valid pointer to a buffer holding a filename without the extension)

The function that called sprintf is declared this way:
Code: [Select]
void RemoveExtensionFromFileName(char *pFileName)
As you can see, the sprintf wanted to overwrite the argument that was passed to RemoveExtensionFromFileName.
Okay, no problem with that, so let's go on step back and see what function called this function.

This is the parent function:
Code: [Select]
SceneLL* MenuFunction_Scene_OpenScene(char* pName)and its call was
Code: [Select]
RemoveExtensionFromFileName(pName);
As you can see, the MenuFunction_Scene_OpenScene function simply passed its argument to the next function.
So let's go one more step back.

MenuFunction_Scene_OpenScene was called by WinMain this way:
Code: [Select]
MenuFunction_Scene_OpenScene("demo1.scn");
So, just to sum it up: the sprintf function tried to overwrite the read-only string "demo1.scn"...
Basically, that's exactly the same problem why halkun's function call crashed.

However, halkun has not sold this bug together with a book  :wink:

 - Alhexx

Cyberman

  • *
  • Posts: 1572
    • View Profile
Re: Pointers are the bane of my existance
« Reply #7 on: 2006-09-21 04:59:51 »
The function that causes the crash is sprintf. It was called this way:
Code: [Select]
sprintf(pFileName,"%s",pName);(Note: pName is a valid pointer to a buffer holding a filename without the extension)

The function that called sprintf is declared this way:
Code: [Select]
void RemoveExtensionFromFileName(char *pFileName)
As you can see, the sprintf wanted to overwrite the argument that was passed to RemoveExtensionFromFileName.
Okay, no problem with that, so let's go on step back and see what function called this function.

This is the parent function:
Code: [Select]
SceneLL* MenuFunction_Scene_OpenScene(char* pName)and its call was
Code: [Select]
RemoveExtensionFromFileName(pName);
As you can see, the MenuFunction_Scene_OpenScene function simply passed its argument to the next function.
So let's go one more step back.

MenuFunction_Scene_OpenScene was called by WinMain this way:
Code: [Select]
MenuFunction_Scene_OpenScene("demo1.scn");
So, just to sum it up: the sprintf function tried to overwrite the read-only string "demo1.scn"...
Basically, that's exactly the same problem why halkun's function call crashed.

However, halkun has not sold this bug together with a book  :wink:

 - Alhexx
A very good point, and halkun isn't cranking out code for his next book either.
Well anyhow I'm glad we found the bug, however which this is why it behoves one to use const char or copy strings as needed when striping an extension etc.  I think the author may have been overcome by his own lazyness.  Either way it's a common mistake, one can spot it if they've done it before :D

Cyb

sfx1999

  • *
  • Posts: 1142
    • View Profile
Re: Pointers are the bane of my existance
« Reply #8 on: 2006-09-21 13:54:14 »
TCL_LINK_STRING

The C variable is of type char *. If its value is not null then it must be a pointer to a string allocated with Tcl_Alloc. Whenever the Tcl variable is modified the current C string will be freed and new memory will be allocated to hold a copy of the variable's new value. If the C variable contains a null pointer then the Tcl variable will read as ``NULL''.

http://www.tcl.tk/man/tcl8.3/TclLib/LinkVar.htm

Also, does "creator" automatically null terminate?
« Last Edit: 2006-09-21 13:56:26 by sfx1999 »

Cyberman

  • *
  • Posts: 1572
    • View Profile
Re: Pointers are the bane of my existance
« Reply #9 on: 2006-09-21 16:22:37 »
The convention for C defines that a string of "" ends in a null at the end of the string.  The latter statement is very important because the end of the string technically is the end of an array.  So
Code: [Select]
char * SillyPutty = { "This is a short sentance." };Will have all the characters plus a null character.  It should NOT be defined though without a const char *... because someone can change it's pointer and the original string... is LOST into oblivion.

As for TCL_Alloc well I guess it's an RTFD event I suppose.


There is no guarentee that the compilor will follow C conventions.

Cyb