C language. Code working in debug mode and not in release why?

Hello.. I'm currently working on a RPG game I use VS 2019 and I use 'C' language.
I am a bit confuse, and I hope you guys make me understand why this is possible. I resolved the problem, and I understood what I did wrong in the code, now is working on both modes (debug/release), but I still need a clarification why in debug mode didn't warning me about the problem , and it was still working while the bug was in the code and in release same bug and NOT working like it should.
This is the function that I wrote where I fixed the bug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
 BOOL checkDesiredTileAndMobs(BOOL* mobFound, int *mobIdx, DIRECTION dir)
{
    BOOL CanMoveToDesiredTiles = FALSE;
    int i = 0;
    
    switch (dir)
    {
        case DOWN:
        {
            for (i = 0; i < MONSTERS_NUMBER; i++)
            {
                if ((gMonster[i].WorldPos.x == gPlayer.WorldPos.x) && ((gMonster[i].WorldPos.y - 16) == gPlayer.WorldPos.y))
                {
                    *mobIdx = i;
                    *mobFound = TRUE;
                    break;
                }
            }
            break;
        }
        case LEFT:
        {
            for (i = 0; i < MONSTERS_NUMBER; i++)
            {
                if (((gMonster[i].WorldPos.x + 16) == gPlayer.WorldPos.x) && (gMonster[i].WorldPos.y == gPlayer.WorldPos.y))
                {
                    *mobIdx = i;
                    *mobFound = TRUE;
                    break;
                }
            }
            break;
        }
        case RIGHT:
        {
            for (i = 0; i < MONSTERS_NUMBER; i++)
            {
                if (((gMonster[i].WorldPos.x - 16) == gPlayer.WorldPos.x) && (gMonster[i].WorldPos.y == gPlayer.WorldPos.y))
                {
                    *mobIdx = i;
                    *mobFound = TRUE;
                    break;
                }
            }
            break;
        }
        case UP:
        {
            for (i = 0; i < MONSTERS_NUMBER; i++)
            {
                if ((gMonster[i].WorldPos.x == gPlayer.WorldPos.x) && ((gMonster[i].WorldPos.y + 16) == gPlayer.WorldPos.y))
                {
                    *mobIdx = i;
                    *mobFound = TRUE;
                    break;
                }
            }
            break;
        }
        default:
            break;
    }

    if (mobFound)
    {
        if (gMonster[i].MobDead == FALSE)
            CanMoveToDesiredTiles = FALSE;
        else CanMoveToDesiredTiles = TRUE;
    }
    else CanMoveToDesiredTiles = TRUE;

    return CanMoveToDesiredTiles;
} 
The bug was here:

1
2
3
4
5
6
7
8
9
10
...
..
.
if (mobFound)
{
    if (gMonster[i].MobDead == FALSE)
        CanMoveToDesiredTiles = FALSE;
    else CanMoveToDesiredTiles = TRUE;
}
else...


if (mobFound) had to be dereferenced if (*mobFound), which in debug mode was working just fine , and I even not realize I did a mistake, still switching to release mode .. I saw the player can move at all, so is a bit hard to fix when you can't use debug tools.
Now I fixed and work just fine, but I just wanted someone can explain why did worked in debug and why not in realease. Thank you..
Just to clarify, line 64 ( if (mobFound) ) is the only line you changed to make the buggy code work? Or were there other lines you changed?

Clarification why in debug mode didn't warning me about the problem
The compiler doesn't know your intention. Maybe in your codebase, it's valid to pass a null pointer as mobFound. Thus, the check for if (mobFound) is a null-pointer check.

and it was still working while the bug was in the code and in release same bug and NOT working like it should.

Most likely, by default, your 'Release' build will have optimizations enabled, while the 'Debug' build will not have optimizations. The optimizer can aggressively change the underlying compiled code, especially if there is undefined behavior in the code.

That being said, I'm not quite sure why changing line 64 alone would make there be a difference in behavior between Debug and Release. Perhaps there's another issue somewhere else in your code. Maybe show how you're calling the function?
Ganado wrote:
I'm not quite sure why changing line 64 alone would make there be a difference in behavior between Debug and Release.

If *mobFound has not been set to TRUE in the code above then i will equal MONSTERS_NUMBER so gMonster[i] on line 66 will be out of bounds.
..Yes I only changed the line 64. and I'm trying to show the best way where I call the function, cause is a bit complicated and it doesn't allow me to paste 60.000+ lines.. still here is where the Player Process Input is calling that function..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
void PPI_Overworld(void)
void PPI_Overworld(void)
{
   // other code..
   if (gPlayer.HeroDead == FALSE)
   {
        if (!gPlayer.MovementRemaining) // PLAYER HASN'T MOVE YET..
		{
			// DOWN
			if ((gMouse_pointer.y > 390 && gGameInput.MouseLeftButtonIsDown && gMouse_pointer.x > 620 && gMouse_pointer.x < 748 && gMouse_pointer.y < 721))
			{
				int i = 0;
				BOOL CanMoveToDesiredTiles = FALSE;

				for (uint8_t Counter = 0; Counter < _countof(gPassableTiles); Counter++)
				{
					if ((gPlayer.WorldPos.y / 16) + 1 > (gOverworld01.TileMap.Height - 1))
						break;

					if (gOverworld01.TileMap.Map[(gPlayer.WorldPos.y / 16) + 1][gPlayer.WorldPos.x / 16] == gPassableTiles[Counter])
					{
						CanMoveToDesiredTiles = checkDesiredTileAndMobs(&mobFound, &i, DOWN);
						break;
					}
				}

				checkDesiredTileAndMonsters(CanMoveToDesiredTiles, mobFound, gPlayer.ScreenPos, i, DOWN);
			}
		}
		else // if movement remaining..
		{
			gPlayer.MovementRemaining -= gPlayer.RunSpeed;
			
			// switch player directions
			switch (gPlayer.Directions)
			{
				case DOWN:
				{
					if (gPlayer.ScreenPos.y < GAME_RES_HEIGHT - 160) // 128
					{
						gPlayer.ScreenPos.y++;
					}
					else
					{
						gCamera.y += gPlayer.RunSpeed;
					}
					gPlayer.WorldPos.y += gPlayer.RunSpeed;

					break;
				}
				case LEFT:
				{
					if (gPlayer.ScreenPos.x > 208)
					{
						gPlayer.ScreenPos.x--;
					}
					else
					{
						if (gCamera.x > 0)
						{
							gCamera.x -= gPlayer.RunSpeed;
						}
						else
						{
							gPlayer.ScreenPos.x--;
						}
					}
					gPlayer.WorldPos.x -= gPlayer.RunSpeed;

					break;
				}
				case RIGHT:
				{
					if (gPlayer.ScreenPos.x < GAME_RES_WIDTH - 208)
					{
						gPlayer.ScreenPos.x++;
					}
					else
					{
						if (gCamera.x < (gOverworldArea.Area.right - 208))
						{
							gCamera.x += gPlayer.RunSpeed;
						}
						else
						{
							gPlayer.ScreenPos.x++;
						}
					}
					gPlayer.WorldPos.x += gPlayer.RunSpeed;

					break;
				}
				case UP:
				{
					if (gPlayer.ScreenPos.y > 160) // 128
					{
						gPlayer.ScreenPos.y--;
					}
					else
					{
						if (gCamera.y > 0)
						{
							gCamera.y -= gPlayer.RunSpeed;
						}
						else
						{
							gPlayer.ScreenPos.y--;
						}
					}
					gPlayer.WorldPos.y -= gPlayer.RunSpeed;

					break;
				}
				default:
					break;
			}

			// SWITCH THE PLAYER MOVEMENT------------------------------------------------------------------|
			switch (gPlayer.MovementRemaining)
			{
				case 15:
				{
					gPlayer.MovedSinceTeleport = TRUE;
					gPlayer.SpriteIndex = 0;
					break;
				}
				case 14: // This case is because when run and step to teleport not trigger at 16 or 15 case
				{
					// Drain Stamina .. when player run
					if (gPlayer.RunSpeed == 2)
					{
						if (gStamina > 0)
						{
							if (gMiscItems[2].stamina == FALSE)
							{
								if (gPlayer.WorldPos.y > 512 || gPlayer.WorldPos.x >= 816)
								{
									gStamina -= gPlayer.RunSpeed;
								}
							}

							if (gStamina <= 0)
							{
								gStamina = 0;
								gPlayer.RunSpeed = 1;
							}
						}
					}

					gPlayer.MovedSinceTeleport = TRUE;
					gPlayer.SpriteIndex = 0;
					break;
				}
				case 12:
				{
					gPlayer.SpriteIndex = 1;
					break;
				}
				case 8:
				{
					gPlayer.SpriteIndex = 0;
					PlayGameSound(&gSoundPlayerSteps);
					break;
				}
				case 4:
				{
					gPlayer.SpriteIndex = 2;
					break;
				}
				case 0:
				{
#ifdef _DEBUG
					// Assert gPlayer movement if out of bounds
					ASSERT(gPlayer.ScreenPos.x % 16 == 0, "gPlayer did not land on a position that is a multiple of 16!");
					ASSERT(gPlayer.ScreenPos.y % 16 == 0, "gPlayer did not land on a position that is a multiple of 16!");
					ASSERT(gPlayer.WorldPos.x % 16 == 0, "gPlayer did not land on a position that is a multiple of 16!");
					ASSERT(gPlayer.WorldPos.y % 16 == 0, "gPlayer did not land on a position that is a multiple of 16!");
#endif // DEBUG

					gPlayer.SpriteIndex = 0;

					if (gOverworld01.TileMap.Map[gPlayer.WorldPos.y / 16][gPlayer.WorldPos.x / 16] == TILE_PORTAL_01)
					{
						if (gPlayer.MovedSinceTeleport == TRUE)
						{
							// Should we run into a random monster encounter..?
							PortalHandler();
						}
					}
					else if ((gPlayer.WorldPos.x == gTownPortal[1].WorldPos.x) && (gPlayer.WorldPos.y == gTownPortal[1].WorldPos.y))
					{
						if (gPlayer.MovedSinceTeleport == TRUE)
						{
							if (gPlayer.skills[1].enable == TRUE)
							{
								TownPortalHandler();
							}
						}
					}
					else if ((gPlayer.WorldPos.x == gTownPortal[0].WorldPos.x) && (gPlayer.WorldPos.y == gTownPortal[0].WorldPos.y))
					{
						if (gPlayer.MovedSinceTeleport == TRUE)
						{
							if (gPlayer.skills[1].enable == TRUE)
							{
								TownPortalHandler();
							}
						}
					}
					else
					{
						if (gPlayer.StepsTaken - gPlayer.StepsTakenSinceLastMonsterEncounter >= RANDOM_MOSTERS_GRACE_PERIOD_STEPS)
						{
							DWORD Random = 0;

							rand_s((unsigned int*)&Random);
							Random = Random % 100;

							if (Random <= gPlayer.RandomEncouterPercentage)
							{
								gPlayer.StepsTakenSinceLastMonsterEncounter = gPlayer.StepsTaken;
								CalculatePlayerProperties();
							}
						}
					}

					gPlayer.StepsTaken++;
					break;
				}
				default:
					break;
			}
		}
   }
   else
   {
	   // Other code..
   }
}
Ganado wrote:
The optimizer can aggressively change the underlying compiled code

Another reason why you might experience different behaviour in debug mode and release mode (assuming you have undefined behaviour in your program) is because Microsoft's compiler will set uninitialised and freed memory to certain bit patterns in debug mode. This is not done in release mode. See https://stackoverflow.com/a/127404

Mif wrote:
CanMoveToDesiredTiles = checkDesiredTileAndMobs(&mobFound, &i, DOWN);

I hope you have (re)set mobFound to false before calling this function because the function assumes that you have. To make it harder to use the checkDesiredTileAndMobs function incorrectly you might want to change it so that it always set *mobFound to false at the beginning, or perhaps rewrite it some other way to avoid this assumption.
Last edited on
If *mobFound has not been set to TRUE in the code above then i will equal MONSTERS_NUMBER so gMonster[i] on line 66 will be out of bounds.

Ahh right, good catch Peter.

So, in the buggy code, "if (mobFound)" was always being entered, but the gMonster[i] dereference was then the undefined behavior because 'i' could be out of bounds if the mob was not found within the "switch(dir)" logic.

Another improvement to the code: Limit the scope of i to the individual for loops. You already set 'mobIdx' when the mob is found, so use that variable in the "if found" logic.
Last edited on
yes I set the mobFound to false .. I just omitted to copy that line in the previous code, I'm so sorry.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void PPI_Overworld(void)
{
   // other code..
   if (gPlayer.HeroDead == FALSE)
   {
        BOOL mobFound = FALSE;

        if (!gPlayer.MovementRemaining) // PLAYER HASN'T MOVE YET..
		{
			// DOWN
			if ((gMouse_pointer.y > 390 && gGameInput.MouseLeftButtonIsDown && gMouse_pointer.x > 620 && gMouse_pointer.x < 748 && gMouse_pointer.y < 721))
			{
				int i = 0;
				BOOL CanMoveToDesiredTiles = FALSE;

				for (uint8_t Counter = 0; Counter < _countof(gPassableTiles); Counter++)
				{
					if ((gPlayer.WorldPos.y / 16) + 1 > (gOverworld01.TileMap.Height - 1))
						break;

					if (gOverworld01.TileMap.Map[(gPlayer.WorldPos.y / 16) + 1][gPlayer.WorldPos.x / 16] == gPassableTiles[Counter])
					{
						CanMoveToDesiredTiles = checkDesiredTileAndMobs(&mobFound, &i, DOWN);
						break;
					}
				}

				checkDesiredTileAndMonsters(CanMoveToDesiredTiles, mobFound, gPlayer.ScreenPos, i, DOWN);
			}
		}
    }
}

Well the mobFound is needed 3 lines after in this function checkDesiredTileAndMonsters(CanMoveToDesiredTiles, mobFound, gPlayer.ScreenPos, i, DOWN);. So based on what checkDesiredTileAndMobs(&mobFound, &i, DOWN); founds, this information must be past far to the other function. cose if you can move to a desired tile , but if you encounter a mob, certainly you can't pass thru him, so you do other stuff. And yes I now realized I have 2 functions with almost same name, but they do different things , so that may confuse you too.
Last edited on
Another improvement to the code: Limit the scope of i to the individual for loops. You already set 'mobIdx' when the mob is found, so use that variable in the "if found" logic.


Yeah that can be improved.. thanks Ganado
If the above is a reply to what I said about the mobFound parameter maybe not being necessary I retract that (I have already removed it from my previous post). It was a mistake.

Do you feel like your original question has been answered? It has been explained why the behaviour between debug and release mode can differ when you have undefined behaviour in your program. It has also been explained what caused the undefined behaviour.
Last edited on
this is not at all uncommon and you MUST test your code in release mode, as it will be compiled when you distribute it. Testing in debug mode is for using the debugger to find issues or first pass smoke tests (does it compile and run at all) only. And yes, there are times when the bug hides in debug mode making the debugger difficult to use. For those cases, adding prints or logging to the code that you can run in release mode is a simple way to work through it.
.. Yes, I understand my buggy code logic fully and I found that quite fast, and repaired, but about the (debug/release) mode I only understood that the compiler makes certain optimizations to the final product and it can have different behaviours from the debug mode. I'll read more about the compiler optimizations. I'm at 90% sure of the understanding, just because of my lack of knowledge about the compiler, which will be my next thing to read.
That being said until next time.. thank you all.
Topic archived. No new replies allowed.