This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Restrict Global Variables to specific modules

Here comes a very special wish - please do not call me crazy:

Working with many C modules, I prefer to define protected/"dangerous" variables static to one module. Quite often I would like to have a variable global only in a restricted number of modules - typically only in one module and then further the the communication modules (e. g. "comm1.c", "comm2.c").

To achieve this, it would be terrifically nice, if I could create the following code with a special pragma modifier (I made the examples with two modules "comm1.c" and "comm2.c", to keep the examples more general - but in fact I would be perfectly happy, if this would work with restriction to one further module "comm.c"):

#pragma RestrictedGlobal "comm1.c"
#pragma RestrictedGlobal "comm2.c"
int iRestricted;

(alternatively also like this (but then it would not work in other C compilers):

#pragma allowAccess "comm1.c"
#pragma allowAccess "comm2.c"
static int iRestricted;


)

In "comm1.c", "comm2.c" then the extern command

extern int iRestricted;


should be allowed - but it any other module it should fail (best would be, if the command itself would be allowed, but if the access to iRestricted would fail in any other module - then it is possible to include the extern declaration in some header file without the danger of failure)

I know that this is not possible in any other C compiler, but I think pragma is not standardized, so C compiler producers can add their own - just I think better use the first of the above alternatives, as then the code will also work in other C compilers not recognizing this pragma.

... I know that this might be quite an effort, as it concerns the very interior of the compiling/linking tables ... so just a polite question ... .

Parents
  • You are wrong if you think dynamic memory is what it takes to get huge advantages from C++.

    C++ don't have more need for dynamic memory than C has. If you receive data in a C program, you need somewhere to store it. Switching to C++, you still need somewhere to store it. It doesn't matter if a variable is a C++ object, or an int or an array of char.

    The main power of C++ is in encapsulation, and creating tight (but extendable) modules combining data and methods. And encapsulation don't have anything to do with how objects are instantiated.

    What we do know, right now, is that you want to rewrite the C language because you aren't happy with it.

    And that you haven't spent any real time with C++ and don't know the real strengths with the language.

    So - time that you start investing some time really learning the language. And thinking about _why_ things looks like they do.

    Consider how classes can have friends.
    Consider the implications of "using" namespaces.
    Or the advantages of type-safe linking.
    Or the ability to use { and } to encapsulate a block of code marked as "C".
    The difference in complexity compared to your #pragmas are huge.

Reply
  • You are wrong if you think dynamic memory is what it takes to get huge advantages from C++.

    C++ don't have more need for dynamic memory than C has. If you receive data in a C program, you need somewhere to store it. Switching to C++, you still need somewhere to store it. It doesn't matter if a variable is a C++ object, or an int or an array of char.

    The main power of C++ is in encapsulation, and creating tight (but extendable) modules combining data and methods. And encapsulation don't have anything to do with how objects are instantiated.

    What we do know, right now, is that you want to rewrite the C language because you aren't happy with it.

    And that you haven't spent any real time with C++ and don't know the real strengths with the language.

    So - time that you start investing some time really learning the language. And thinking about _why_ things looks like they do.

    Consider how classes can have friends.
    Consider the implications of "using" namespaces.
    Or the advantages of type-safe linking.
    Or the ability to use { and } to encapsulate a block of code marked as "C".
    The difference in complexity compared to your #pragmas are huge.

Children
  • And that you haven't spent any real time with C++ and don't know the real strengths with the language.

    Sorry, but that's not right - in Windows I am C++ expert and fan.

    I am far from rewriting C language. I just wanted to add a helpful programming aid to the linker.

  • One further point:

    If I use a global class in Keil ARM C++, and if this class has a constructor - is there some way to tell the compiler when the constructor should be called? (I mean e. g. somewhere in the assembly-startup function, in the c-startup function, or at the start of the main function?)

  • But if your #pragma only handles the next variable declaration, you could need a huge number of #pragma - and each one can be wrong.

    Think about C++ which can use { and } to group information into a namespace or class. Or have private:, public:, protected: to have lots of things into a specific access category.

    If you think dynamic allocation of objects is a requirement to get the full power of C++, then you may have used C++, but you have not considered the full richness of the language compared to C.

    Think about a circular buffer with enquque(), dequeue(), is_empty(), is_full(), have_data(). Duplicated multiple times for multiple serial ports. And having the constructor receive a pointer to a char buffer and a buffer size. So you can instantiate - statically - multiple ring buffers of different size, for UART0 receive, UART0 transmit, UART1 receive, UART1 transmit, ..., SPI0 receive, SPI0 transmit, ... Zero dynamic memory involved. Unless you decide that you have one big block of buffer memory, and a configuration table to decide how many bytes of the pool for UART0 receive, UART0 transmit, ...

    Or let's say that you have a class with a state machine supporting different sets of blinks - slow, fast, dual-blink, ... And a virtual method for driving an output. So you have one instance driving LED-1, one for LED-2, one for REL-1, one for REL-2, ... And the code runs through all these objects (accessed as an array of pointers) and call an update() method - automagically getting all your blinks etc. No dynamic memory involved. With C, you would have to send a pointer to a struct containing configuration and current tick counters, and a function pointer for what output to drive.

    Dynamic memory is for solving problems where you don't know how many of something you need. But with limited RAM, you can't just allocate more objects. So there must be limits. And limits means that the problem could have been solved statically. Or that you know that you either need x objects of type 1, or y objects of type 2 - but not at the same time. C or C++ doesn't matter. You still have to write the software based on the existing hw - and select the used hw based on needs. But operator new is not what "makes" C++. The main purpose of it is to allow many programs to share a big processor and have each individual program claim no more than what is needed. With smaller microcontrollers, there are only one program running. So no memory to save for another program. So rules for how to share the available RAM can be decided at design time - without hurting the capabilities of the programming language.

  • A global class? A class is a data type. As expert C++ programmer, I would have expected you to talk about a global object - i.e. an instantiation of a class.

    Well - the startup code have to make sure that the constructor is called before the code enters main(). Just as the startup code have to make sure that the interval variables in the C library gets initial values after the startup code have mapped RAM but before you reach main().

    If you want the constructor to be called after you enter main(), then you should create the object as an auto object on the stack. That allows main() to contain code that makes decisions before the constructor is called. So you could decide what parameters to send to the constructor.

  • ok ok you are starting to convince me ...

    but do you perhaps also have an answer on my new question? (possibility to define the exact time point, when the constructor of a global class is called? - if I cannot influence this, I would prefer to somehow not allow constructors for global classes ... (in Windows programming this is one of the rules: never use a global class, otherwise you might run in strange problems ... if here I HAVE TO use global classes (at least for the classes with much data, because I do not want dynamic memory allocation), then I would at least like to have this "global class constructor problem" under tight control))).

  • But this reply is not very precise.

    If you say, that the "constructor will be called before main"... You know that in an ARM software quite a bit is happening before main, especially all the core initialisation and the PLL clock startup. Can I be sure, that the compiler will be smart enough to call such global constructors after the basic core initialisation and PLL clock startup code? (how should the compiler recognize this code ...?)

  • But you have realized that the ARM processor don't care much about global memory or stack memory - so you can create objects on the stack?

    You can even do something like:

    void main(void) {
        if (configured_as_x) {
            CFunctionality1 app;
            app.Run();
        } else {
            CFunctionality2 app;
            app.Run();
        }
    }
    

  • If you put PLL initialization inside main(), then you know the global objects will be constructed before the PLL is initialized.

    But an assembler startup file will not initialize any global C++ objects randomly. So you can spend whatever time you want setting up PLL etc before you let the startup script call the functions that initializes variables, constructs objects etc.

    You may get an initial copy of a startup file when you create a project. But after that, you own that file. You can do whatever you like with it, before you let it call the finial init function(s) that setups the runtime library, global variables etc before main() is called.