Pointer Question

Started by RickJ, Sat 12/03/2005 22:06:23

Previous topic - Next topic

RickJ

If "control" is null then the following test generates a runtime error. 
   if (control.OwningGUI.Controls[0].AsButton!=null) {
      // Do Something
   }

So then is it necessary to use a nested if statement to check each portion of the path for null or is there a better way?
   if (control!=null) {
      if (control.OwningGUI!=null) {
         if (control.OwningGUI.Controls[0]=null) {
            if (control.OwningGUI.Controls[0].AsButton!=null) {
               // Do Something
            }
         }
      }
   }

Is it the case that AsButton or Controls{x} generates the runtime error and if so perhaps just returning null would be a better choice, since the result of those operations is a pointer.  Runtime errors would ultimately be generated if such a null pointer is subsequently used in a context that warrants it?

This seems to me to be very reminiscent to the recent discussion about lazy evaluation. 

Pumaman

Quote from: RickJ on Sat 12/03/2005 22:06:23
So then is it necessary to use a nested if statement to check each portion of the path for null or is there a better way?

Well, you only need to check the parts of the path that could be null.

For example, "control" in your example -- I presume it's a variable you've declared in your script. Therefore you'll know whether it can be null or whether it will always contain a valid pointer.

The OwningGUI will never return null (since a control always has an owning GUI), therefore you don't have to check it.

Controls[0] and AsButton can return null (if there are no controls on the GUI, and if it's not a button, respectively) so you'd need to check those.

Still, it depends on the context of what you're doing. The rest of your script surrounding that line of code might already do sufficient checks that you don't need to check things again. It all depends on the situation.

RickJ

Quote
Well, you only need to check the parts of the path that could be null.
That was pretty much the meat of my question. Thanks for clarifying what was going on.   Perhaps the manual should make this point.

In my opinion, a path should evaluate to null if any portion of it is null instead of generating runtime errors.  This would allow a simple test of the entire path as in my first example.   

However, now that I have a clearer understaning of the situation I can do what is necessary to avoid the possibility of runtime errors.  Thanks.   

strazer

QuoteIn my opinion, a path should evaluate to null if any portion of it is null instead of generating runtime errors.

I agree.

Pumaman

The thing is, it's not possible to do that a lot of the time.

Suppose you did this:

int width = control.OwningGUI.Controls[0].AsButton.Width;

what should it do if Controls[0] is null? It can't just return the path as null, because that's not compatible with an int variable.

Snarky

Quote from: strazer on Sun 13/03/2005 03:28:57
QuoteIn my opinion, a path should evaluate to null if any portion of it is null instead of generating runtime errors.

I agree.

No, no, please no.

That would completely violate programming conventions, it would confuse C++/Java/C# programmers no end, and it would make it much more difficult to track down pointer errors.

In programming, it's a good thing to be "fail-fast". That means that if something has gone wrong (for instance, the object I've dereferenced doesn't exist), make it crash now, instead of giving me some kind of response that isn't what I expect and will either make it crash later or behave incorrectly.

RickJ

In this case ...

int width = control.OwningGUI.Controls[0].AsButton.Width;

the path would evaluate to null and the resulting exptression would be the equivalent to ...

int width = null.Width;

which would then generate a runtime error. 

So what I am suggesting is that evaluation of pointer paths would not generate runtime errors but would instead evaluate to null if the given path was not valid.  Subsequent usage (in the same or subsequent script staements) of such a path to access member data or functions would be subject to runtime error checking as it is now.



stuh505

Quoteint width = control.OwningGUI.Controls[0].AsButton.Width;

the path would evaluate to null and the resulting exptression would be the equivalent to ...

int width = null.Width;

If  "control.OwningGUI.Controls[0].AsButton" is the path that evaluates to NULL in your proposed change,
then wouldn't "control.OwningGUI.Controls[0]" be the path that evaluates to NULL in the following statement,

Quotecontrol.OwningGUI.Controls[0].AsButton

causing it to evaluate to null.AsButton, causing another runtime error, which is what you wanted to avoid in the first place?

I think it would make more sense to just have the entire path evaluate to NULL...because that makes some logical sense, and null.something never makes sense.

I'm also curious why int is not compatible with NULL...but it probably has something to do with ints in AGS storing their status in the save game...

Traveler

Quote from: RickJ on Sun 13/03/2005 17:40:02
In this case ...

int width = control.OwningGUI.Controls[0].AsButton.Width;

the path would evaluate to null and the resulting exptression would be the equivalent to ...

int width = null.Width;

which would then generate a runtime error. 

So what I am suggesting is that evaluation of pointer paths would not generate runtime errors but would instead evaluate to null if the given path was not valid.  Subsequent usage (in the same or subsequent script staements) of such a path to access member data or functions would be subject to runtime error checking as it is now.

This would be a horrible thing to allow. I completely agree with Snarky: this would open the door to such debugging nightmare that you cannot possibly imagine (unless you dealt with it in the past. If you haven't, believe me, you don't want to.) It's very simple to contruct exceptional cases, to which you'll have to make new exception rules: if the exceptional case happens, this is the return value, etc.

It's best to have runtime errors as early in development as possible so that they can be debugged/fixed before it hits the user. In your example, you should always know where your code is being called and based on that you can opt for skipping checks. If you cannot know for some reason, then you need to check.

And even if CJ decides to implement this (which in itself would probably be a nightmare, to hardwire the compiler to the "correct" behavior), the game engine's performance will suffer a lot. Right now, a check is made if you make it in your code - if you know that a check can be avoided, you avoid it and there'll be no check done. If this thing gets implemented, the engine has no idea when a check can be skipped, so it'll have to check every variable every time you access it. That's going to kill performance.

RickJ

I believe that C-Language behaves as I describe.  I can't remember if I ever had occasion to try somnething like this myself or not so I did some research on the net.   I wasn't able to find an explicit statement of how C is supposed to behave in this situation but I found an example from Juciy Studio that illustrates my point.

In the example below the queue is initialized so that  "queue->front->next" is set to NULL.  Now what happens if the leave() function is called immediately after initialization?  The leave function checks to see if anything is left in the queue via "if (queue->front->next->next == NULL)".   Since the queue is empty queue->front->next is clearly NULL.  As I understand Traveler's and Snarky's remark they would expect the "if" statement to generate an exception at runtime, which would mean that this example is incorrect.

I don't have a C-compiler on this machine, perhaps someone would like to make a test and let us know what happens.  At this point I am very curious.

Queue Formation
A queue is a special kind of data structure that restricts access to its elements. It does this by only allowing insertions to take place at the rear of the list, and deletions to take place at the front of the list. A queue is characterised by the expression First In, First Out (FIFO). An analogy of a queue isn't required as we spend most of our time stuck in the things. You join at the back, and leave at the front (unless you're bigger than the person in front of you). To implement a queue, we shall use an extra structure to keep track of the front and rear of the queue.
typedef struct node
{
    char data;          /* Store the keystroke by the user */
    struct node *next;  /* Pointer to the next node */
} NODE;

typedef struct queue
{
    NODE *front;        /* Front of the queue */
    NODE *rear;         /* Back of the queue */
} QUEUE;

Initialising an Empty Queue
By making the front pointer equal the rear pointer, we create a dummy node as the queue members front and rear are both of type NODE.
int initialise(QUEUE *queue)
{
    if ((queue->front = (NODE *)malloc(sizeof(NODE))) == NULL)
        return 0;
    queue->rear = queue->front;
    queue->front->next = NULL;
    return 1;
}

Removing Items from the Queue
We will write a leave function to remove a character from the queue. Start by creating an old node template, oldnode, of type NODE. Make the oldnode point to the first item in the queue, bypassing the dummy node. By moving the next member of the front node to the next node, we can free the oldnode. If the node is the last node in the queue, we need to ensure the rear of the queue point to the front of the queue (an empty queue).
char leave(QUEUE *queue)
{
    NODE *oldnode;
    char key;
    oldnode = queue->front->next;
    key = oldnode->data;
    /* Check if removing the last node from the queue */
    if (queue->front->next->next == NULL)
        queue->rear = queue->front;
    else
        queue->front->next = queue->front->next->next;
    free(oldnode);
    return key;
}


P.S. Sorry for the long post  :-[

strazer

Oh my.
I'm no programmer so I can't judge the viability of Rick's proposal, but his "null.Width" example sounds perfectly reasonable to me.

Pumaman

My personal leaning is with Snarky and Traveler on this one. It's standard coding behaviour that you'd expect -- if you do something like that in Java, C#, C or C++ you'll get an error (a NullPointerException in Java/C#, or the program will crash in C/C++). Even Visual Basic will generate an error if you do this.

However, I can understand that from a game scripting point of view, there may be some benefit to be had from allowing null pathing in the manner that Rick suggests. It would have to basically say that a null pointer can access a pointer property of itself, and that will automatically return null. Rather messy to implement, and I'm still not convinced of whether I should or not.

QuoteI'm also curious why int is not compatible with NULL...but it probably has something to do with ints in AGS storing their status in the save game...

An int is a number. null is an undefined pointer. It simply does not make sense to convert null to or from a number. In C, NULL is equivalent to 0, but this doesn't make sense in a strongly-typed langauge -- and indeed Java/C# don't allow it.

Snarky

Quote from: RickJ on Mon 14/03/2005 08:30:02
In the example below the queue is initialized so thatÃ,  "queue->front->next" is set to NULL.Ã,  Now what happens if the leave() function is called immediately after initialization?Ã,  The leave function checks to see if anything is left in the queue via "if (queue->front->next->next == NULL)".Ã,  Ã, Since the queue is empty queue->front->next is clearly NULL.Ã,  As I understand Traveler's and Snarky's remark they would expect the "if" statement to generate an exception at runtime, which would mean that this example is incorrect.

char leave(QUEUE *queue)
{
Ã,  Ã,  NODE *oldnode;
Ã,  Ã,  char key;
Ã,  Ã,  oldnode = queue->front->next;
Ã,  Ã,  key = oldnode->data;
Ã,  Ã,  /* Check if removing the last node from the queue */
Ã,  Ã,  if (queue->front->next->next == NULL)
Ã,  Ã,  Ã,  Ã,  queue->rear = queue->front;
Ã,  Ã,  else
Ã,  Ã,  Ã,  Ã,  queue->front->next = queue->front->next->next;
Ã,  Ã,  free(oldnode);
Ã,  Ã,  return key;
}


Rick, I would actually expect it to crash on the preceding line:

    key = oldnode->data;


Because oldnode is set to queue->front->next which is NULL if the queue is empty. NULL->data doesn't make sense, and should crash.

I don't see this as the example being incorrect so much as you shouldn't try to take an element out of an empty queue. In true C style, making sure you're not doing something stupid is left as an exercise to the user.

Traveler

Quote from: RickJ on Mon 14/03/2005 08:30:02
char leave(QUEUE *queue)
{
Ã,  Ã,  NODE *oldnode;
Ã,  Ã,  char key;
Ã,  Ã,  oldnode = queue->front->next;
Ã,  Ã,  key = oldnode->data;
Ã,  Ã,  /* Check if removing the last node from the queue */
Ã,  Ã,  if (queue->front->next->next == NULL)
Ã,  Ã,  Ã,  Ã,  queue->rear = queue->front;
Ã,  Ã,  else
Ã,  Ã,  Ã,  Ã,  queue->front->next = queue->front->next->next;
Ã,  Ã,  free(oldnode);
Ã,  Ã,  return key;
}

RickJ, this example assumes that there are at least two items in the queue. If there is only the top item, it would crash on the line "key = oldnote->data;" (as pointed out by Snarky.) Don't forget, that this is just an MSDN sample - many MSDN samples don't even compileÃ,  ::) , so don't take it at face value.

In C, the instant you hit a null pointer access, the code crashes. It is not even C that does this, it is the processor itself: it is illegal in Intel processors to address anything in the lowest 64 kByte of memory. If the processor itself sees something trying to access any memory in that memory range, it throws a hardware exception.

AGS script code is of course not running directly on the processor (the AGS engine sees the code first, so it can take evasive action) but one still shouldn't abuse this fact. Accessing a null pointer is a fundamental error because code tries to access something that doesn't exits. It's perfectly legal for code to return NULL (in C anyway, not sure about AGS) - how do you then differentiate between a default null (that really masks an error) and a null that was returned normally. In most cases you may want to do very different things in answer to these two null-s that will look the same: null. If later on AGS scripting starts supporting creating object instances and creating internal classes (if CJ has something like that in mind), having default null-s will make it pretty much impossible. (Or there will be a lot of pain at that time to get rid of the places where everyone relies on default nulls.)

If doing all the checks is slow (in terms of performance) then we should ask CJ to spend some time optimizing AGS instead of adding new features. If it's inconvenient to add the code for the checks, we can try to smarten up intellisense or whip up some editor addins that help with inserting code templates for if-s.

Again, it'd be extremely hard to track down errors because of such a default behavior. Right now, if you access null, the code crashes, so you see immediately that something's wrong. With the default behavior you'll get default null-s all over the place, so it may not turn out that there is something very wrong with your code - until some user reports it. Doing a fix right now pretty much translates to downloading the entire game again so it's a high risk to let out code with possible hidden errors. I know this feature is very tempting and looks convenient but it would cause a lot of probles down the road.

Sorry for the long post, but this is really something we should avoid.

RickJ

Traveler thanks for your thoughtful reply, however I think every one here is missing the point.   Accessing non-existent entities is not an issue, I think everyone agrees that you can't do that and that generating an error at run-time is the appropiate action.   It's not relevant to the discussion.

It's my contention that a pointer value of null is legal and that one should be able to test for it without generating a runime error and that "control.OwningGUI.Controls[0].AsButton" is a pointer.

I'm well aware of the Intel Archeticture as I did quite a lot of ASM programing during my time at General Electric.  I believe you are mistaken about the processor throwing null pointer access exceptions though.  Programs are given specific sections of memory they are allowed to access.  If a program attempts to access other memory an interrupt is generated that represents a runtime error.   A null pointer access may or may not casue this to happen, it depends upon the language implementation.  This is irrelevant anyway because this is not what casuses "control.OwningGUI.Controls[0].AsButton" to generate a runtime error, the error is generated by the AGS interpreter.   

You and Snarky mention performance hits and engine bloat associated with evaluating pointer expressions as I suggest.  I don't believe this is much of an issue either.  It seems to me AGS is already checking for null when evaluating pointer expressions, otherwise it couldn't generate the runtime error.   Although I can't be certain I don't think returning  NULL is any more performane intensive or complicated than generating a runtime error.   

Quote
Again, it'd be extremely hard to track down errors because of such a default behavior. Right now, if you access null, the code crashes ...
Access null?  Again I think this misses the point and that you are not understanding the issue.   Please explain to me where I have said that accessing  something with a null pointer should be allowed.  I haven't.  What I have said is that "control.OwningGUI.Controls[0].AsButton" is an exprression that represents a pointer and that if it doesn't point to anything for any reason it should evaluate to a value of null. 

Quote
With the default behavior you'll get default null-s all over the place, so it may not turn out that there is something very wrong with your code - until some user reports it. Doing a fix right now pretty much translates to downloading the entire game again so it's a high risk to let out code with possible hidden errors. I know this feature is very tempting and looks convenient but it would cause a lot of probles down the road.
Actually, the situation is just the opposite as you describe and this is the reason I submitted this humble suggestion.  I believe that people will find that something like "if (control.OwningGUI.Controls[0].AsButton==null)"  casuses an error and then they will make changes until the error goes away by doing something like "if (control==null)", leaving an incomplete test and future possibilites of game crash. 

Quote
It's standard coding behaviour that you'd expect -- if you do something like that in Java, C#, C or C++ you'll get an error (a NullPointerException in Java/C#, or the program will crash in C/C++). Even Visual Basic will generate an error if you do this.
Really, I'm quite surprised by this.  I guess I have always been careful and disciplined in pointer usage and so have never came across something like this before now (or at least don't remember if I have).  Hehe, I'll have to try it for myself one of these days just to convince myself that I'm not living in the "Twilight Zone" or something.   Anyway I think the bottom line on this is that you should do whatever you think is best for AGS and it's users.  Ans as always I'll leave to that your your own good judgement. 

InCreator

#15
Sorry for writing totally offtopic thing here, but...

Does a casual AGSer either know or need that stuff?
Pointers? Pathing?

From 'dumb' side of view - AGS has gone very complex lately.
New updates consist mostly complex scripting improvements, and I actually don't believe that much people use them.

Like, it's still 2D graphics, AGS editor layout looks alot or totally like it was version before and so on. There's very little of changes to get really hands-on, if you're not a programmer.


I'm not discouraging code improvements. But It would be cool to actually see what this or that improvement allows to do.
Like some tech showcase, tutorials or whatever could demonstrate it better.

***
I'm an artist, not a programmer (though I haw microscopic experience with C++ and Basic) - so I'm sorry if my post was totally stupid.


Traveler

Quote from: RickJ on Tue 15/03/2005 16:02:09
It's my contention that a pointer value of null is legal and that one should be able to test for it without generating a runime error and that "control.OwningGUI.Controls[0].AsButton" is a pointer.
"control.OwningGUI.Controls[0].AsButton" is actually a series of pointers (it doesn't represent a single pointer), so if any pointer along the chain is null, you should get a runtime error. To get to the last pointer, the engine has to navigate through the entire object chain.

Quote
I'm well aware of the Intel Archeticture as I did quite a lot of ASM programing during my time at General Electric.Ã,  I believe you are mistaken about the processor throwing null pointer access exceptions though. ...Ã,  This is irrelevant anyway because this is not what casuses "control.OwningGUI.Controls[0].AsButton" to generate a runtime error, the error is generated by the AGS interpreter.Ã, 
You're right, it's been a while I did any asm programmingÃ,  :)

Quote
It seems to me AGS is already checking for null when evaluating pointer expressions, otherwise it couldn't generate the runtime error.Ã,  Ã, Although I can't be certain I don't think returningÃ,  NULL is any more performane intensive or complicated than generating a runtime error.
I'm not sure it does but then I don't have that much experience with AGS scripting yet. It may very well be that the engine simply crashes when it encounters a null pointer, but as I said, I don't have experience here. CJ could tell us but it's probably better not to know such implementation details.

Quote
Access null?Ã,  Again I think this misses the point and that you are not understanding the issue.Ã,  Ã, Please explain to me where I have said that accessingÃ,  something with a null pointer should be allowed.
I probably used the wrong terms in trying to explain my point: since the expression "control.OwningGUI.Controls[0].AsButton" isn't a single pointer, but a series of pointers, any sub-object in this chain is a pointer. So you need to check all the ones which are not guaranteed to be non-null, otherwise the code tries to access a nonexistent object. I didn't mean that you said accessing nulls, I apologize if it appeared like that.

Quote
they will make changes until the error goes away by doing something like "if (control==null)", leaving an incomplete test and future possibilites of game crash.
You're right, but this is a question of documentation. Introducing pointers give the game programmer a lot of flexibility but it also opens the door to all kinds of unavoidable problems. Creating a shortcut doesn't solve the problem, it just hides it and it'll be that much worse when it comes to fixing bugs.

I do, however, agree with you, that CJ will have to make the call.

Pumaman

If you do this:

control.OwningGUI.Controls[0].AsButton

then the AGS compiler breaks it down like this:

GUI *temp1 = control.OwningGUI;
GUIControl *temp2 = temp1.Controls[0];
Button *temp3 = temp2.AsButton;

In order to implement your suggestion, it could be quite complex for the compiler to work out that it needs to check for null and then skip the property call, rather than making it.

At the moment the AGS Engine does check for null usage -- that's why you get a "Null pointer referenced" error if you access a null pointer. However, it would require a fair amount of extra compiler logic to say "if this is null, skip this call and return null in its place".

Performance isn't really an issue -- the last time I profiled AGS, the script interpreter was responsible for about 6% of the total game CPU time. Put simply, that means that if you completely removed all your game scripts, you'd expect the frame rate to rise from 40 to 42 FPS. Hardly worth worrying about.

Finally, allowing null pathing as you suggest could easily confuse people. Let's take this for example:

GUI *myGui;
// set myGui to something
GUIControl* theButton = myGui.Controls[0];

suppose that myGui was null. This would mean that theButton got set to null. We'd then get people asking questions like "AGS is broken, I've definitely got a control on this GUI, but Controls[0] is returning null" when in actual fact it was the myGui variable that was null.

Whereas, with the current compiler, doing the code above would error out with a Null Pointer error on that line, so that it's obvious where the problem is.

So, overall I'm still not sure. In a way I like the suggestion, since it stops people having to check everything for null all the time. However, the scenarious where people will be iterating through GUI controls and so forth are very few. 99% of the time, people will not use pointer paths longer than one dot and will use defined variable names that are guaranteed not to be null, so I doubt it would be too big of an issue.

I think overall the potential confusion caused by adding this feature probably outweighs its usefulness.

QuoteSorry for writing totally offtopic thing here, but...

Does a casual AGSer either know or need that stuff?
Pointers? Pathing?

From 'dumb' side of view - AGS has gone very complex lately.
New updates consist mostly complex scripting improvements, and I actually don't believe that much people use them.

While the new version does add some complex scripting improvements, it also significantly changes basic scripting, make it much more intuitive and easier to use. If you write any script code at all in your game (and let's face it, 99% of people do) you'll notice changes.

However, as I say the topic of this thread is more advanced and most people needn't be bothered by it.

Kweepa

I'm just going to chime in on the side of Snarky, Traveler, and CJ here.
If you're counting votes. :=
Still waiting for Purity of the Surf II

RickJ

Quote
I think overall the potential confusion caused by adding this feature probably outweighs its usefulness.
Hehe, this answers the original question,  :) and I can live with that.   

SMF spam blocked by CleanTalk