|
Functions returning NULL pointers when I expect otherwise |
NeoFahrenheit
Member #16,644
March 2017
|
Hi guys, Sorry for the silly question, but I can't figure it out what's going on. But my MS Visual Studio debugger is showing that all my timer, queue, disp and font pointers are NULL when reaching the main loop. I am mostly a begginer in C, so sorry if I missed something. Code are in the attachments. Can you guys help? |
bamccaig
Member #7,536
July 2006
|
Just posting the code so we can read it easier... Quote:
1#include <stdio.h>
2#include <allegro5/allegro5.h>
3#include <allegro5/allegro_font.h>
4
5void init_all(ALLEGRO_TIMER* timer, ALLEGRO_EVENT_QUEUE* queue, ALLEGRO_DISPLAY* disp, ALLEGRO_FONT* font)
6{
7 if (!al_init())
8 {
9 printf("couldn't initialize allegro\n");
10 exit(1);
11 }
12
13 if (!al_install_keyboard())
14 {
15 printf("couldn't initialize keyboard\n");
16 exit(1);
17 }
18
19 timer = al_create_timer(1.0 / 60.0);
20 if (!timer)
21 {
22 printf("couldn't initialize timer\n");
23 exit(1);
24 }
25
26 queue = al_create_event_queue();
27 if (!queue)
28 {
29 printf("couldn't initialize queue\n");
30 exit(1);
31 }
32
33 disp = al_create_display(800, 600);
34 if (!disp)
35 {
36 printf("couldn't initialize display\n");
37 exit(1);
38 }
39
40 font = al_create_builtin_font();
41 if (!font)
42 {
43 printf("couldn't initialize font\n");
44 exit(1);
45 }
46
47 al_register_event_source(queue, al_get_keyboard_event_source());
48 al_register_event_source(queue, al_get_display_event_source(disp));
49 al_register_event_source(queue, al_get_timer_event_source(timer));
50}
1#pragma once
2
3// Inicialize all basic essentials.
4void init_all(ALLEGRO_TIMER*, ALLEGRO_EVENT_QUEUE*, ALLEGRO_DISPLAY*, ALLEGRO_FONT*);
1#include <stdio.h>
2#include <allegro5/allegro5.h>
3#include <allegro5/allegro_font.h>
4#include "init.h"
5
6int main()
7{
8 ALLEGRO_TIMER* timer = NULL;
9 ALLEGRO_EVENT_QUEUE* queue = NULL;
10 ALLEGRO_DISPLAY* disp = NULL;
11 ALLEGRO_FONT* font = NULL;
12
13 init_all(&timer, &queue, &disp, &font);
14
15 bool done = false;
16 bool redraw = true;
17 ALLEGRO_EVENT event;
18
19 al_start_timer(timer);
20 while (1)
21 {
22 al_wait_for_event(queue, &event);
23
24 switch (event.type)
25 {
26 case ALLEGRO_EVENT_TIMER:
27 // game logic goes here.
28 redraw = true;
29 break;
30
31 case ALLEGRO_EVENT_KEY_DOWN:
32 case ALLEGRO_EVENT_DISPLAY_CLOSE:
33 done = true;
34 break;
35 }
36
37 if (done)
38 break;
39
40 if (redraw && al_is_event_queue_empty(queue))
41 {
42 al_clear_to_color(al_map_rgb(0, 0, 0));
43 al_draw_text(font, al_map_rgb(255, 255, 255), 0, 0, 0, "Hello world!");
44 al_flip_display();
45
46 redraw = false;
47 }
48 }
49
50 al_destroy_font(font);
51 al_destroy_display(disp);
52 al_destroy_timer(timer);
53 al_destroy_event_queue(queue);
54
55 return 0;
56}
You appear to be passing a pointer-to-a-pointer, but the function is accepting just a pointer here: Quote:
ALLEGRO_TIMER* timer = NULL; ALLEGRO_EVENT_QUEUE* queue = NULL; ALLEGRO_DISPLAY* disp = NULL; ALLEGRO_FONT* font = NULL; init_all(&timer, &queue, &disp, &font);
Which means instead of assigning to the variables in main you're just overwriting your own local variables (throwing away the parameter value). You probably want to change the signature of init_all to accept pointer-to-pointer instead. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
NeoFahrenheit
Member #16,644
March 2017
|
Thanks for the help bamcaig, but I don't know how to do that. I tried modifying the function all the ways I could, but I'm terrible using pointer-to-pointer. If it isn't too much trouble for you, could you help me with code examples? Thanks! EDIT: I have made it work, but it was only luck and a lot of try and error. If you guys could enlighten me, It would be awesome. My updated code are in the attachments. |
bamccaig
Member #7,536
July 2006
|
Instead of attaching a few files it is much nicer to just embed the code in your post so we don't have to download the files (if it's more than 3 files then probably zipping them is preferred). You can format code by posting it in an XML tag: <code name="foo.c">teh codes</code>. The name attribute is optional. It's actually really impressive if you figured that out just with trial and error. But pointers to pointers work the same way that regular pointers do, except that the data they point to is another pointer. In your example, you allocated variables in main to hold the pointers to the structures, and then passed a pointer to those variables to a function to allow you to modify them from within the function. int x; int * x_pointer = &x; int * y_pointer = malloc(sizeof(int)); int ** y_pointer_to_pointer = &y_pointer; When you dereference (`*varname`) a pointer-to-a-pointer you reference the original pointer. For example, *y_pointer_to_pointer would return the address stored at y_pointer. First it looks up the address of y_pointer, and then it loads size int bytes) starting there, which we've promised will hold a pointer to another integer. By passing pointers to local variables you can allow a function to modify those local variables. This can be utilized for multiple return values, for example. It's a common pattern for initialization functions if you're trying to avoid globals, though storing those values all in a structure can simplify the interface. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
NeoFahrenheit
Member #16,644
March 2017
|
Thanks for the explanation, bamccaig. I'm afraid I will ask a bit more of you. So, I have made a example code showing my doubts: 1#include <stdio.h>
2
3void swap(int* a, int* b)
4{
5 int temp = *a;
6
7 *a = *b;
8 *b = *a;
9}
10
11int main()
12{
13 int x = 10, y = 5;
14 swap(&x, &y);
15
16 return 0;
17}
18
19/* The example above shows the avarage use of a pointer function. That I can understand.
20But I remember when I was in my first semester in university, I had to pass pointer-to-pointer
21to functions dealing with trees or graphs if I want to modify them without returning the pointer.
22
23Why?
24
25The explanation that I have always got is that x and y variables are restricted to the main function.
26swap can't acess them directly, so we need to pass their adresses. But with data structures or with that
27allegro5 case, I was already sending (or so I tought) the pointers to init_all(), so why can't the main
28function retrieve them? Why do we need to use pointer-to-pointer in some cases? */
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
http://www.cplusplus.com/doc/tutorial/pointers/ Do some reading and be enlightened. Pointers are normal variables, except they hold an address. Now to modify a variable, you need its address. (&var). So if you want to modify a pointer outside of its scope, you need it's address. int a = 0; int* pa = &a; int** ppa = &pa; So to modify pa indirectly, you need to pass ppa. Which is a pointer to pointer. All that means is that it holds the address of a pointer, so it can be modified. EDIT My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
bamccaig
Member #7,536
July 2006
|
A pointer allows you to modify the data that it points at. 1#include <stdio.h>
2
3void triplei(int n)
4{
5 n = n * 3;
6}
7
8void triplep(int * n)
9{
10 *n = *n * 3;
11}
12
13int main(int argc, char *argv[])
14{
15 int x = rand() % 100;
16 int y = x;
17
18 triplei(y);
19
20 printf("Triple %d is %d? (fail)\n", x, y);
21
22 y = x;
23
24 triplep(&y);
25
26 printf("Triple %d is %d? (win)\n", x, y);
27
28 return 0;
29}
In the above example, triplei has no effect on y. The reason is because it accepted a copy of y's value. It has no way to modify y itself. When we assign a new value to n in triplei it has no effect on y. Contrarily, triplep works. Instead of accepting a copy of y it accepts a copy of y's memory address. By dereferencing the memory address we can read the value stored there, and we can also write to it! First we read the value, then multiply it by 3, and then overwrite the value stored at the memory address with the result. This does modify y. In this example, our data was just a simple integer, but it works exactly the same way with a more complicated structure. 1#include <stdio.h>
2#include <stdlib.h>
3#include <time.h>
4#include <unistd.h>
5
6struct my_timer_
7{
8 int _start;
9 int _current;
10};
11
12typedef struct my_timer_ my_timer_t;
13
14my_timer_t * create_my_timer(void)
15{
16 // Allocating memory on the heap so it will still exist when we return.
17 my_timer_t * ptr = malloc(sizeof(my_timer_t));
18
19 if (ptr == NULL)
20 {
21 return NULL;
22 }
23
24 ptr->_start = (unsigned int)time(NULL); // Contrived implementation.
25 ptr->_current = ptr->_start;
26
27 return ptr;
28}
29
30void tick_my_timer(my_timer_t * timer)
31{
32 timer->_current = (unsigned int)time(NULL);
33}
34
35void destroy_my_timer(my_timer_t ** timer)
36{
37 free(*timer);
38 *timer = NULL;
39}
40
41void initializep(my_timer_t * timer)
42{
43 timer = create_my_timer();
44}
45
46void initializepp(my_timer_t ** timer)
47{
48 *timer = create_my_timer();
49}
50
51int main(int argc, char * argv[])
52{
53 my_timer_t * timer = NULL;
54
55 initializep(timer);
56
57 printf("Timer is initialized by initializep? %s\n",
58 timer == NULL ? "no" : "yes");
59
60 initializepp(&timer);
61
62 printf("Timer is initialized by initializepp? %s\n",
63 timer == NULL ? "no" : "yes");
64
65 if (timer != NULL)
66 {
67 sleep(2);
68
69 tick_my_timer(timer);
70
71 printf("Timer has ticked? %s\n",
72 timer->_current - timer->_start ? "yes" : "no");
73
74 destroy_my_timer(&timer);
75 }
76
77 return 0;
78}
Similarly, initializep does not work to initialize timer in main. And it also leaks memory! The timer variable in initializep starts out as a copy of the value of timer in main (i.e., NULL). When we allocate memory and assign it to timer in initializep all it does is store the new memory address in initializep. It doesn't affect main. Whereas initializepp is able to actually assign to timer in main because we passed a copy of the memory addresss of timer in main. By dereferencing the pointer-to-a-pointer we can read the timer pointer value, but we can also write to it because we're actually pointing at the pointer! For the same reason, destroy_timer is able to actually assign NULL back to timer in main because we passed a pointer to it. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
NeoFahrenheit
Member #16,644
March 2017
|
Things are more clearer now. I think my mistake was assuming functions that takes pointers as arguments creates it's local variable with the adresses it recevies. This is how I was doing in 1.jpeg. But I tought, maybe, all variables inside functions has it's own unique adresses. That made a lot more sense. Besides, all the variables must be allocated before runtime. Using this tought, I made 2.jpeg. I'm not really sure if I was really getting it or just wanting and manipulating my hand and pencil to get it. I think a little bit of more time and revisiting my notes will be enough to the knowlogde to stick. Thanks a lot for your help, bamccaig. |
bamccaig
Member #7,536
July 2006
|
Actually not all variables are allocated before runtime. Only global/static variables that exist always get allocated in that way (technically they're still allocated at runtime, but I'm speculating that they're allocated as part of the memory that holds the actual program; I'm not sure about that though). Local variables in functions are allocated when the function is called. There are generally two memory sources for programs: the stack and the heap. The stack is where your function calls and variables and return values are stored. The heap is where dynamic memory is allocated (e.g., my_timer_t example above uses malloc to allocate memory on the heap for a timer structure). The program always has a pointer to the top of the stack. When a function is called the program jumps to some code that sets up the function call by allocating a fixed amount of memory based on how many and how big the parameters, variables, and return types are, with a few extra slots for housekeeping. Then it jumps to the actual code for the function and begins executing it. The code is executing in reference to where the function call was allocated on the stack. It generally knows that variable1 is X bytes from the reference point. It can access the variable location in memory by doing pointer arithmetic with that reference point. When the function returns there is code injected into the program to copy the return value back to the calling function and effectively unallocate/free the stack memory used for the previous function call. So the location of local variables in memory varies depending on where it's stack was allocated and when the function is called in the program. If it's called from many different places in the call stack then its variables will exist at different places in memory depending on which call is executing. That call will know where they are, but the rest of the program doesn't. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|