NXC: Tasks and global variables

Discussion specific to NXT-G, NXC, NBC, RobotC, Lejos, and more.
roscohead
Posts: 18
Joined: 29 Sep 2010, 03:09

NXC: Tasks and global variables

Post by roscohead »

I'm just wondering how array struct members are shared between tasks in NXC? In the code below, the foobar task always reports the array size as zero, even after it has been initialised to length 3 in the main task. Is there any way I can get foobar to recognise the initialised array?

ROSCO

Code: Select all

#include "NXCDefs.h"

struct s1 {
       int a1[];
};

int v0[] = {1,2,3};

s1 v1;

int foo1(s1& vx) {
    int i2 = vx.a1[2];
    NumOut(0,8,i2);
    NumOut(0,16,ArrayLen(vx.a1));
    Wait (2000);
    return 0;
}

int foo(s1& vx) {
    ArrayInit(vx.a1, 0, 3);
    for (int i=0; i<3; i++) {
        vx.a1[i] = v0[i];
    }
    foo1(vx);
    return 0;
}

int bar(s1& vx) {
    NumOut(0,24,ArrayLen(vx.a1));
    return 0;
}

task foobar() {
     while(1>0) {
          bar(v1);
     }
}

task main() {
     start foobar;
     foo(v1);
     int i1 = v1.a1[2];
     NumOut(0,0,i1);
     Wait(2000);
}
spillerrec
Posts: 358
Joined: 01 Oct 2010, 06:37
Location: Denmark
Contact:

Re: NXC: Tasks and global variables

Post by spillerrec »

All variables are shared between tasks normally, no matter which form or type it has. It is shared with no safety, several task can read and write to the variable at the same time.
It becomes a bit different when used in functions. Even though you pass the variables as reference, it actually passes it by copy instead. When the function returns, it copies the variable back into the original. This is because NXC doesn't support pointers. (So there is no point in passing it by reference unless you are actually modifying it.)

I can't test your program as my NXT just ran out of batteries...
So if you are getting weird results with a program like this, it is most likely some multitasking bug. When you are not protecting your variables way too much can go wrong. And since true references doesn't exists in NXC, the copies it makes under the hood can complicate this even further.
Long story short: Do it without multitasking when possible (i.e. most cases), otherwise do protect your variables with mutexes. (And be aware of how variables are passed into functions!)


Some notes to the program:
You should use Precedes() instead of start(), as you are likely to run into issues with start()/stop() as I have heard. Precedes() will start the tasks after the current task exits, so normally main() is an almost empty task which just starts the others.
Also, please give the tasks/variables some proper names, even a short program like this becomes hard to read. When using NumOut()/TextOut() use the variables LCD_LINE1 to LCD_LINE8 instead.
You don't need the #include as it is added automatically btw. And instead of "while(1>0)" just use "while(true)".
My blog: http://spillerrec.dk/category/lego/
RICcreator, an alternative to nxtRICeditV2: http://riccreator.sourceforge.net/
roscohead
Posts: 18
Joined: 29 Sep 2010, 03:09

Re: NXC: Tasks and global variables

Post by roscohead »

spillerrec wrote:... it actually passes it by copy instead. When the function returns, it copies the variable back into the original. This is because NXC doesn't support pointers. ...
Hmmm this may be a clue, I'm guessing copying back the struct does not copy the array member that was built. Interesting, because it works fine if I use a global array variable rather than the array member of the global struct variable. I guess there's some other housekeeping for the struct that doesn't get done.

Looks like I can't use an array in a struct like that, makes my code a bit messier, but should still be doable.

ROSCO
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: NXC: Tasks and global variables

Post by afanofosc »

I am not seeing this problem with appropriate task switching (waits/yields in each task) and a few other changes. If the array in a struct is not being copied properly then it is a firmware bug. Are you using the latest enhanced NBC/NXC firmware?

Code: Select all


struct s1 {
       int a1[];
};

int v0[] = {1,2,3};

s1 v1;

safecall void print(const byte line, s1 vx)
{
  ClearLine(line);
  NumOut(80, line, ArrayLen(vx.a1));
  NumOut(0, line, vx.a1[0]);
  NumOut(30, line, vx.a1[1]);
  NumOut(60, line, vx.a1[2]);
}

int bar(s1& vx) {
    print(LCD_LINE8, vx);
    return 0;
}

task foobar() {
     while(1>0) {
          bar(v1);
          Wait(MS_10);
     }
}

int foo1(s1& vx) {
    print(LCD_LINE4, vx);
    Wait(SEC_2);
    return 0;
}

int foo(s1& vx) {
    ArrayInit(vx.a1, 0, 3);
    for (int i=0; i<3; i++) {
        vx.a1[i] = v0[i];
    }
    print(LCD_LINE3, vx);
    foo1(vx);
    return 0;
}

task two() {
  Wait(MS_5);
  while(true)
  {
     print(LCD_LINE1, v1);
     foo(v1);
     print(LCD_LINE2, v1);
     Wait(SEC_2);
  }
}

task main() {
     Precedes(foobar, two);
     foo(v1);
//     ArrayInit(v1.a1, 1, 3);
//     v1.a1 *= 2;
}
John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
roscohead
Posts: 18
Joined: 29 Sep 2010, 03:09

Re: NXC: Tasks and global variables

Post by roscohead »

I'm using FW 1.28.

Indeed, in my original version, if I change start foobar to Precedes(foobar), it works correctly, so it seems it matters when the array is created. If task foobar starts before it's created, it always seems to see zero length in foobar (or any functions called by foobar). Also, if the length of the array is changed AFTER foobar starts, foobar continues to see the original length:

Code: Select all

struct s1 {
       int a1[];
};

int v0[] = {1,2,3};

s1 v1;
int a1[];

safecall int foo1(s1& vx) {
    int i2 = vx.a1[1];
    int i3 = a1[1];
    NumOut(0,8,i2);
    NumOut(0,16,ArrayLen(vx.a1));
    NumOut(20,8,i3);
    NumOut(20,16,ArrayLen(a1));
    Wait (2000);
    return 0;
}

safecall int foo(s1& vx, int s) {
    ArrayInit(vx.a1, 0, s);
    for (int i=0; i<s; i++) {
        vx.a1[i] = v0[i];
    }
    ArrayInit(a1, 0, s);
    for (int i=0; i<s; i++) {
        a1[i] = v0[i];
    }
    foo1(vx);
    return 0;
}

safecall int bar(s1& vx) {
    NumOut(0,24,ArrayLen(vx.a1));
    NumOut(20,24,ArrayLen(a1));
    return 0;
}

task foobar() {
     while(true) {
          bar(v1);
     }
}

task two() {
     Wait(200);
     foo(v1,2);
}

task main() {
     Precedes(foobar, two);
     //start foobar;
     foo(v1,3);
     int i1 = v1.a1[1];
     NumOut(0,0,i1);
     //Wait(2000);
}
Also interesting, when I run your code, it prints the contents correctly, but the ArrayLen prints as zero! If I change the print function to:

Code: Select all

safecall void print(const byte line, s1 vx)
{
  ClearLine(line);
  int s = ArrayLen(vx.a1);
  NumOut(80, line, s);
  for (int i=0; i<s; i++) {
    NumOut(i*30, line, vx.a1[i]);
  }
}
the contents are not printed! So even though your version does print the contents, the array size still seems to be incorrect?

ROSCO
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: NXC: Tasks and global variables

Post by afanofosc »

Rosco,

I can't tell if by "FW 1.28" you mean the standard LEGO firmware or the enhanced NBC/NXC firmware.

I would strongly recommend grabbing the latest test release which includes the enhanced NBC/NXc firmware version 1.31. You may be having trouble with a signed/unsigned assignment bug in the version of the compiler that you are using. Can you check the generated NBC code and see if it looks right?

I just fixed another firmware defect related to float/unsigned long MOV operations. That fix will be in the next official release which is imminent.

John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
roscohead
Posts: 18
Joined: 29 Sep 2010, 03:09

Re: NXC: Tasks and global variables

Post by roscohead »

Thanks John, apologies, it was the enhanced 1.28. I've downloaded test release 20110720, and loaded 1.31 from that into the NXT, but your program still displays the ArrayLen as 0 in all cases (but displays the correct array contents). Another interesting note though, if I change foo() to take a size parameter, and re-build it with size 2 in task two(), it barfs when it tries to display the 3rd element, so it knows the array is length 2 even though the ArrayLen still returns 0:

Code: Select all


struct s1 {
       int a1[];
};

int v0[] = {1,2,3};

s1 v1;

safecall void print(const byte line, s1 vx)
{
  ClearLine(line);
  NumOut(80, line, ArrayLen(vx.a1));
  NumOut(0, line, vx.a1[0]);
  NumOut(30, line, vx.a1[1]);
  NumOut(60, line, vx.a1[2]);
}

int bar(s1& vx) {
    print(LCD_LINE8, vx);
    return 0;
}

task foobar() {
     while(1>0) {
          bar(v1);
          Wait(MS_10);
     }
}

int foo1(s1& vx) {
    print(LCD_LINE4, vx);
    Wait(SEC_2);
    return 0;
}

int foo(s1& vx, int s) {
    ArrayInit(vx.a1, 0, s);
    for (int i=0; i<s; i++) {
        vx.a1[i] = v0[i];
    }
    print(LCD_LINE3, vx);
    foo1(vx);
    return 0;
}

task two() {
  Wait(MS_5);
  foo(v1,2);
  while(true)
  {
     print(LCD_LINE1, v1);
     print(LCD_LINE2, v1);
     Wait(SEC_2);
  }
}

task main() {
     Precedes(foobar, two);
     foo(v1,3);
//     ArrayInit(v1.a1, 1, 3);
//     v1.a1 *= 2;
}
How do I inspect the generated NBC code?

ROSCO
mattallen37
Posts: 1818
Joined: 02 Oct 2010, 02:19
Location: Michigan USA
Contact:

Re: NXC: Tasks and global variables

Post by mattallen37 »

roscohead wrote:How do I inspect the generated NBC code?

ROSCO
Compile (F5 or F6) then F12.
Matt
http://mattallen37.wordpress.com/

I'm all for gun control... that's why I use both hands when shooting ;)
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: NXC: Tasks and global variables

Post by afanofosc »

With the version of BricxCC/NBC that I have (not released either as a test or official release yet) I do not have a 0 value returned by ArrayLen. So I packaged up what I have into a new test release and uploaded it to sourceforge. Please try this out - along with the new firmware in the zip - and let me know if you still have problems:

http://sourceforge.net/projects/bricxcc ... 111024.zip

I will put this up in the test release folder this evening, i.e., at http://bricxcc.sourceforge.net/test_releases/.

A full official release of NBC 1.2.1 r5 and BricxCC 3.3.8.10 should be ready by the end of the month, if not sooner.

I modified the print function in the above program slightly so that after adjusting the array length down to 2 it does not kill the program. See below:

Code: Select all

safecall void print(const byte line, s1 vx)
{
  ClearLine(line);
  int len = ArrayLen(vx.a1);
  NumOut(80, line, len);
  NumOut(0, line, vx.a1[0]);
  NumOut(30, line, vx.a1[1]);
  if (len > 2)
    NumOut(60, line, vx.a1[2]);
}
I have also tested the above program (with the change to the print function) using the BricxCC test release dated 2011-07-20 and it works fine for me with that version. So can I please see your NBC/NXC options from the Preferences dialog?
bopts.jpg
Perhaps you are not using the internal compiler or a different optimization level?

John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
roscohead
Posts: 18
Joined: 29 Sep 2010, 03:09

Re: NXC: Tasks and global variables

Post by roscohead »

Yes, optimisation level was 2, and I don't use -q (NXT doesn't beep on successful download?). Those were the only differences.

So I experimented with different optimisation levels:

0: ArrayLen displays different number each time - always a multiple of 8.
1: Same as 0.
2: ArrayLen always displays zero.
3: Same as 2.
4: Same as 2.
5: Same as 2.
6: Same as 2.

All other compile options are identical, so there must be something else different here.

I also checked the NBC code (included below). One weird thing I noticed, in the print() routine, in section that prints out the ArrayLen(), there is the following:

Code: Select all

	arrsize __D0print, __print_7qG2_vx_7qG2_000.a1
...
	mov __D0print, __DU0print
...
	numtostr __TextOutArgs.Text, __D0print
It seems to put the array len into _D0print, then overwrite it with a variable _DU0print if I'm reading it right? That seems wrong, and none of the other NumOut()s seem to do it. _DU0print is declared, but never initialised, hence why I get the zeroes, but why is it doing that assignment?

ROSCO

Code: Select all

dseg	segment
;------- definitions -------
__foo_7qG2_vx_7qG2_000_def	struct
a1	sword[]
__foo_7qG2_vx_7qG2_000_def	ends
Location_def	struct
X	sword
Y	sword
Location_def	ends
__TextOutArgs_def	struct
Result	sbyte
Location	Location_def	
Text	byte[]
Options	dword
__TextOutArgs_def	ends
__bar_7qG2_vx_7qG2_000_def	struct
a1	sword[]
__bar_7qG2_vx_7qG2_000_def	ends
__print_7qG2_vx_7qG2_000_def	struct
a1	sword[]
__print_7qG2_vx_7qG2_000_def	ends
__foo1_7qG2_vx_7qG2_000_def	struct
a1	sword[]
__foo1_7qG2_vx_7qG2_000_def	ends
v1_def	struct
a1	sword[]
v1_def	ends
;------- declarations -------
__signed_stack_001foo	sdword	
__signed_stack_002foo	sdword	
__D0two	sdword	
__D0print	sdword	
__DU0print	dword	
__signed_stack_001foobar	sdword	
__print_mutex	mutex
__Wait_7qG2_ms_7qG2_000_inline_two	dword	
__unsigned_stack_002print	dword	
__signed_stack_001print	sdword	
__signed_stack_003print	sdword	
__D0foobar	sdword	
__D0bar	sdword	
__D0foo	sdword	
__TextOutMutex	mutex
__D0foo1	sdword	
__ArrHelper__print_73_0	sword	
__foo_7qG2_s_7qG2_000	sword	
__ArrHelper__foo_73_0	sword	
__foo_7qG2_i_7qG2_001	sword	
__bar_return	byte	
__ClearLine_7qG2_line_7qG2_000_inline_print	byte	
__print_return	byte	
__foo1_return	byte	
__foo_return	byte	
__constVal1	sbyte	1
____initialize_global_data_return	byte	
__zffoo	byte	
__zffoobar	byte	
__constVal0	sbyte	
__zftwo	byte	
__print_7qG2_line_7qG2_000	byte	
__foo_7qG2_vx_7qG2_000	__foo_7qG2_vx_7qG2_000_def	
__TextOutArgs	__TextOutArgs_def	
__bar_7qG2_vx_7qG2_000	__bar_7qG2_vx_7qG2_000_def	
__print_7qG2_vx_7qG2_000	__print_7qG2_vx_7qG2_000_def	
__foo1_7qG2_vx_7qG2_000	__foo1_7qG2_vx_7qG2_000_def	
v1	v1_def	
v0	sword[]	0x1, 0x2, 0x3
__BlankLine	byte[]	'                    '
dseg	ends
;------- code -------
thread main
	subcall __initialize_global_data, ____initialize_global_data_return
	mov __foo_7qG2_vx_7qG2_000, v1
	set __foo_7qG2_s_7qG2_000, 3
	subcall foo, __foo_return
	mov v1, __foo_7qG2_vx_7qG2_000
	exit 0, 1
endt
;------------------------
subroutine print
	mov __ClearLine_7qG2_line_7qG2_000_inline_print, __print_7qG2_line_7qG2_000
#pragma macro 7
	acquire __TextOutMutex
	mov __TextOutArgs.Location.X, __constVal0
	mov __TextOutArgs.Location.Y, __ClearLine_7qG2_line_7qG2_000_inline_print
	mov __TextOutArgs.Options, __constVal0
	mov __TextOutArgs.Text, __BlankLine
	syscall 13, __TextOutArgs
	release __TextOutMutex
	arrsize __D0print, __print_7qG2_vx_7qG2_000.a1
	set __signed_stack_001print, 80
	mov __unsigned_stack_002print, __print_7qG2_line_7qG2_000
	mov __D0print, __DU0print
	acquire __TextOutMutex
	mov __TextOutArgs.Location.X, __signed_stack_001print
	mov __TextOutArgs.Location.Y, __unsigned_stack_002print
	set __TextOutArgs.Options, 0
	numtostr __TextOutArgs.Text, __D0print
	syscall 13, __TextOutArgs
	mov __D0print, __TextOutArgs.Result
	release __TextOutMutex
	set __signed_stack_001print, 0
	mov __unsigned_stack_002print, __print_7qG2_line_7qG2_000
	set __signed_stack_003print, 0
	index __ArrHelper__print_73_0, __print_7qG2_vx_7qG2_000.a1, __signed_stack_003print
	mov __D0print, __ArrHelper__print_73_0
	acquire __TextOutMutex
	mov __TextOutArgs.Location.X, __signed_stack_001print
	mov __TextOutArgs.Location.Y, __unsigned_stack_002print
	set __TextOutArgs.Options, 0
	numtostr __TextOutArgs.Text, __D0print
	syscall 13, __TextOutArgs
	mov __D0print, __TextOutArgs.Result
	release __TextOutMutex
	set __signed_stack_001print, 30
	mov __unsigned_stack_002print, __print_7qG2_line_7qG2_000
	set __signed_stack_003print, 1
	index __ArrHelper__print_73_0, __print_7qG2_vx_7qG2_000.a1, __signed_stack_003print
	mov __D0print, __ArrHelper__print_73_0
	acquire __TextOutMutex
	mov __TextOutArgs.Location.X, __signed_stack_001print
	mov __TextOutArgs.Location.Y, __unsigned_stack_002print
	set __TextOutArgs.Options, 0
	numtostr __TextOutArgs.Text, __D0print
	syscall 13, __TextOutArgs
	mov __D0print, __TextOutArgs.Result
	release __TextOutMutex
	set __signed_stack_001print, 60
	mov __unsigned_stack_002print, __print_7qG2_line_7qG2_000
	set __signed_stack_003print, 2
	index __ArrHelper__print_73_0, __print_7qG2_vx_7qG2_000.a1, __signed_stack_003print
	mov __D0print, __ArrHelper__print_73_0
	acquire __TextOutMutex
	mov __TextOutArgs.Location.X, __signed_stack_001print
	mov __TextOutArgs.Location.Y, __unsigned_stack_002print
	set __TextOutArgs.Options, 0
	numtostr __TextOutArgs.Text, __D0print
	syscall 13, __TextOutArgs
	mov __D0print, __TextOutArgs.Result
	release __TextOutMutex
	subret __print_return
ends
;------------------------
subroutine bar
#pragma safecalling
	acquire __print_mutex
	set __print_7qG2_line_7qG2_000, 0
	mov __print_7qG2_vx_7qG2_000, __bar_7qG2_vx_7qG2_000
	subcall print, __print_return
	release __print_mutex
	set __D0bar, 0
	subret __bar_return
	subret __bar_return
ends
;------------------------
thread foobar
__NXC_Label_642:
	set __signed_stack_001foobar, 1
	set __D0foobar, 0
	cmp 1, __zffoobar, __signed_stack_001foobar, __D0foobar
	mov __D0foobar, __zffoobar
	brtst 4, __NXC_Label_643, __zffoobar
	mov __bar_7qG2_vx_7qG2_000, v1
	subcall bar, __bar_return
	mov v1, __bar_7qG2_vx_7qG2_000
	wait 10
	jmp __NXC_Label_642
__NXC_Label_643:
	exit -1, -1
endt
;------------------------
subroutine foo1
#pragma safecalling
	acquire __print_mutex
	set __print_7qG2_line_7qG2_000, 32
	mov __print_7qG2_vx_7qG2_000, __foo1_7qG2_vx_7qG2_000
	subcall print, __print_return
	release __print_mutex
	wait 2000
	set __D0foo1, 0
	subret __foo1_return
	subret __foo1_return
ends
;------------------------
subroutine foo
	arrinit __foo_7qG2_vx_7qG2_000.a1, __constVal0, __foo_7qG2_s_7qG2_000
	set __foo_7qG2_i_7qG2_001, 0
__NXC_Label_654:
	mov __signed_stack_001foo, __foo_7qG2_i_7qG2_001
	mov __D0foo, __foo_7qG2_s_7qG2_000
	cmp 0, __zffoo, __signed_stack_001foo, __D0foo
	mov __D0foo, __zffoo
	brtst 4, __NXC_Label_655, __zffoo
	jmp __NXC_Label_656
__NXC_Label_657:
	add __foo_7qG2_i_7qG2_001, __foo_7qG2_i_7qG2_001, __constVal1
	jmp __NXC_Label_654
__NXC_Label_656:
	mov __signed_stack_001foo, __foo_7qG2_i_7qG2_001
	mov __signed_stack_002foo, __foo_7qG2_i_7qG2_001
	index __ArrHelper__foo_73_0, v0, __signed_stack_002foo
	mov __D0foo, __ArrHelper__foo_73_0
	replace __foo_7qG2_vx_7qG2_000.a1, __foo_7qG2_vx_7qG2_000.a1, __signed_stack_001foo, __D0foo
	jmp __NXC_Label_657
__NXC_Label_655:
#pragma safecalling
	acquire __print_mutex
	set __print_7qG2_line_7qG2_000, 40
	mov __print_7qG2_vx_7qG2_000, __foo_7qG2_vx_7qG2_000
	subcall print, __print_return
	release __print_mutex
	mov __foo1_7qG2_vx_7qG2_000, __foo_7qG2_vx_7qG2_000
	subcall foo1, __foo1_return
	mov __foo_7qG2_vx_7qG2_000, __foo1_7qG2_vx_7qG2_000
	set __D0foo, 0
	subret __foo_return
	subret __foo_return
ends
;------------------------
thread two
	set __Wait_7qG2_ms_7qG2_000_inline_two, 5
	wait 5
__NXC_Label_674:
	set __D0two, 1
	tst 5, __zftwo, __D0two
	brtst 4, __NXC_Label_675, __zftwo
#pragma safecalling
	acquire __print_mutex
	set __print_7qG2_line_7qG2_000, 56
	mov __print_7qG2_vx_7qG2_000, v1
	subcall print, __print_return
	release __print_mutex
#pragma safecalling
	acquire __print_mutex
	set __print_7qG2_line_7qG2_000, 48
	mov __print_7qG2_vx_7qG2_000, v1
	subcall print, __print_return
	release __print_mutex
	set __Wait_7qG2_ms_7qG2_000_inline_two, 2000
	wait 2000
	jmp __NXC_Label_674
__NXC_Label_675:
	exit -1, -1
endt
;------------------------
subroutine __initialize_global_data
	subret ____initialize_global_data_return
ends
;------------------------
Post Reply

Who is online

Users browsing this forum: No registered users and 9 guests