Microcontrollers are often programmed bare metal , meaning your program has to initialize the CPU and RAM to get your program going. This is called a startup script and some IDEs/compilers may hide it from the user at first glance.
The startup script does the bare minimum to get to a C main, combined with the C "environment". That is (optionally sometimes) filling RAM with zeros, and most importantly copying variables with predefined values from FLASH to RAM. E.g. if you declare a (global) variable with an initial value, it is stored in program FLASH and copied to RAM before main is called.
I suspect you use XC16 or C30 compiler. You can find these scripts in XC16/v*.**/src/libpic30.zip in the files crt0_standard.s. It contains the following (simplified for this example) start-up code:
mov #__SP_init,w15 ; initialize w15
mov #__SPLIM_init,w14
mov w14,_SPLIM ; initialize SPLIM
nop ; wait 1 cycle
mov #__enable_fixed, w0
cp0 w0
bra z, CORCON_RESET
mov #0x0010,w0
mov w0,CORCON ; enable super saturation and clear IF
CORCON_RESET:
rcall __psv_init ; initialize PSV
mov #__dinit_tbloffset,w0 ; w0,w1 = template
mov #__dinit_tblpage,w1 ;
rcall __data_init_standard ; initialize data
mov #__has_user_init,w0
cp0 w0 ; user init functions?
bra eq,1f ; br if not
call __user_init ; else call them
1:
call _main ; call user's main()
.pword 0xDA4000 ; halt the simulator
reset ; reset the processor
Even if you don't speak assembler fluently you should see what is going on. The script initializes the stack, some CPU registers, initialize PSV & data memory (e.g. copying pre-initialized variable contents from FLASH to RAM), and then call your main function.
The script more or less expects the main will never return, which is why you often see a while(1) at the end of any embedded program.
If it does return for some reason, it will execute the reset instruction. This reinitializes everything again and eventually call main again. This turns into the behavior you're seeing, namely it's looping.