il y a 6 ans
This thread is for a discussion about how people handle global variables - specifically for the Gamebuino (there are hundreds of threads out on tinternet about how to handle them in C or C++ or even other Arduino boards).
As a .NET programmer I wouldn't normally have any globals - I'd create classes etc... however I very quickly ran out of RAM with this approach so it seems the suggested approach is to use #define or const so it doesn't use RAM (I hope that's the correct interpretation).
I went down a little PROGMEM hole for a while until I saw this comment in another thread.. "PROGMEM is obsolete, the keyword does nothing. By defining something as const it will land already in flash and you can just access it normally," - Sorunome ("Images" Discussion)
So what I want opinions on is:
1) const vs. #define? does it matter?
2) sorting out how to break up my files nicely into functional areas but share some globals?
3) whether to put things into .ino vs .cpp vs .h files?
4) how to not get into a mess with my #include declarations?
I've found these links helpful to define the file types but they don't address the globals issue at all.
http://www.visualmicro.com/page/User-Guide.aspx?doc=INOs-and-CPPs.html
NEW il y a 6 ans
Here are my thoughts.
Strictly speaking, neither consts or defines can be used for variables. They can only be used for contant values that do not vary. Having said that, I suggest to use consts for constant values, as they offer type-safety. The compiler is able to better warn you if you do something potentially wrong. Defines have their place though, mainly during pre-processing, where they enable things that cannot be done otherwise.
A typical example where defines are useful is preventing that declarations in a header file are parsed more than once. The general pattern is the following:
// Utils.h #ifndef __UTILS_INCLUDED #define __UTILS_INCLUDED // Utils declarations #end
This enables the following:
Without the defines, FooBar.h would indirectly include Utils.h twice, and give errors that its types and functions are declared more than once.
Typically you declare global variables as follows:
// Game.h extern uint8_t numLives;
// Game.cpp uint8_t numLives;
This way, any code that needs access to the variable numLives, includes Game.h. The code in Game.cpp actually defines the variable, ensuring it gets allocated in the RAM. This enables you to break-up your code any way you want. For example, you can have Bar.cpp include Foo.h and Foo.cpp include Bar.h when Bar and Foo have mutual dependencies. Generally though, you want to avoid that, and have a clean dependency structure. Doing so will make your code easier to modify and extend.
Finally, instead of exposing primitive variables directly, you can use classes:
// Game.h class Game { int8_t numLives; // Signed to enable easy game over check. public: void playerDied() { numLives--; } bool isGameOver() { numLives < 0; } }; extern Game game;
Here you expose a game object, which you can use to indirectly manipulate the numLives variable (and possibly other game-state variables). Using encapsulation like this will make it easier to refactor your code.
Note, when methods are very small, you can define them in the header file, as is done above. This is shorter, but also enables the compiler to inline the code. Typically, however, you define methods in the corresponding .cpp file.
strangenikolai
il y a 6 ans
Thanks for this!
This is exactly how I'm trying to refactor my code at the moment - however I'm running into issues where each cpp file needs consts or types from another and I think I'm getting into a circular #include mess...
Is there a pattern of designing the various files that you would recommend?
At the moment I'm trying to split it into functional areas rather than by any kind of dependency hierarchy. (I also don't have any classes because I initially thought we were coding in C not C++... but that's a whole other thing)
NEW il y a 6 ans
I'd add some little things to the previous already very informative post.
Arduino compiler supports #pragma once
(as almost every recent compiler), so there's no need to use the #define
guards for header files. One "#pragma once
" at the beginning of these files is enough. It's simpler, and avoid mispelling the defined name (which is a classical build mistake).
Do what you do with you C# habits : don't rely on global variables. You'll regret it at some point. Constants are ok.
To asnswer the question 3) : I find .ino very unpracticale as soon as you program grows a little and you want to separate concerns. .ino are done to make a LED blink on an Arduino, not much more. So what I do is using only the .ino file to get advantage of the setup() / loop() pair and call code which is in .cpp/.h files.
NEW il y a 6 ans
Thanks for this!
This is exactly how I'm trying to refactor my code at the moment - however I'm running into issues where each cpp file needs consts or types from another and I think I'm getting into a circular #include mess...
Is there a pattern of designing the various files that you would recommend?
At the moment I'm trying to split it into functional areas rather than by any kind of dependency hierarchy. (I also don't have any classes because I initially thought we were coding in C not C++... but that's a whole other thing)
Sorunome
il y a 6 ans
you could do something like a globals.h
/ globals.cpp
for the commonly shared globals
alxm
il y a 6 ans
I organize my code into self-contained modules that each deal with only one thing. A module in this case is a C file & H file pair. Examples could be Player.c & Player.h, Bullet.c & Bullet.h, etc. The C file contains global variables and function implementations, and the H file exposes the module's public API (usually constants and functions) for other modules to use. I like to keep global variables local to the C file and not let other modules use them directly.
For example, Bullet.h declares a function void createBullet(int x, int y);
that's implemented in Bullet.c, and Player.c includes Bullet.h and calls createBullet(playerX, playerY)
when you press the Gamebuino's A button. Bullet.c keeps track of all bullets in a global array, but that array is not exposed to any other modules. Instead, Bullet.h declares another API function, void handleBullets(void);
, that the game loop calls every frame to update all the bullets.
This way it's easier to keep track of what your code does and where and how it's used.
alxm
il y a 6 ans
I'm running into issues where each cpp file needs consts or types from another and I think I'm getting into a circular #include mess...
That can happen easy :-) Here are some tips to keep it under control:
#pragma once
or #ifdef
guards to prevent the same header from being included multiple times (which would cause redefinition errors).NEW il y a 6 ans
you could do something like a globals.h
/ globals.cpp
for the commonly shared globals
NEW il y a 6 ans
I organize my code into self-contained modules that each deal with only one thing. A module in this case is a C file & H file pair. Examples could be Player.c & Player.h, Bullet.c & Bullet.h, etc. The C file contains global variables and function implementations, and the H file exposes the module's public API (usually constants and functions) for other modules to use. I like to keep global variables local to the C file and not let other modules use them directly.
For example, Bullet.h declares a function void createBullet(int x, int y);
that's implemented in Bullet.c, and Player.c includes Bullet.h and calls createBullet(playerX, playerY)
when you press the Gamebuino's A button. Bullet.c keeps track of all bullets in a global array, but that array is not exposed to any other modules. Instead, Bullet.h declares another API function, void handleBullets(void);
, that the game loop calls every frame to update all the bullets.
This way it's easier to keep track of what your code does and where and how it's used.
NEW il y a 6 ans
I'm running into issues where each cpp file needs consts or types from another and I think I'm getting into a circular #include mess...
That can happen easy :-) Here are some tips to keep it under control:
#pragma once
or #ifdef
guards to prevent the same header from being included multiple times (which would cause redefinition errors).Sorunome
il y a 6 ans
If you have circular dependencies between types, do forward declarations. This is analogous to function prototype vs. implementation: you declare the type before including other headers, but define it after you include the other headers. See these links:
Correct separation of .h
and .cpp
files does this already for you. The forward declaration is in the header, the actual implementation in the cpp. That is the entire purpose of splitting up .h and .cpp and then only including the .h files.
That is also why the .h files aren't supposed to do anything at all, no variable declarations etc.
NEW il y a 6 ans
If you have circular dependencies between types, do forward declarations. This is analogous to function prototype vs. implementation: you declare the type before including other headers, but define it after you include the other headers. See these links:
Correct separation of .h
and .cpp
files does this already for you. The forward declaration is in the header, the actual implementation in the cpp. That is the entire purpose of splitting up .h and .cpp and then only including the .h files.
That is also why the .h files aren't supposed to do anything at all, no variable declarations etc.