2

I'm building a minishell in C, and have come to a roadblock that seems that it could be easily fixable by using global variables(3 to be exact). The reason I think globals are necessary, is that the alternative would be to pass these variables to almost every single function in my program.

The variables are mainargc, mainargv, and shiftedMArgV. The first two are the number of arguments, and argument list passed to main, respectively. The variable shiftedMArgV is the argument list, however it may have been shifted. I'm trying to create builtin functions shift and unshift, and would make shiftedMArgV point to different arguments.

So, would it be stupid to just make these global? Otherwise I will have to revise a very large amount of code, and I'm not sure I'd be losing anything by making them global.

If I do make them global, would doing so from the main header file be foolish?

Thanks for the help guys, if you need any clarification just ask.

Optimus_Pwn
  • 97
  • 1
  • 7
  • 1
    "C Appropriate Use of Global Vars" - I'd say that one does not exist. –  Nov 03 '12 at 22:28
  • 2
    @H2CO3: counter-argument by example — `stdout`, `stdin`, `stderr`. – Jonathan Leffler Nov 03 '12 at 22:33
  • @JonathanLeffler Well, I'd also argue about that. (I never liked using those globals. I seriously feel the program's integrity is lost when I use them.) –  Nov 03 '12 at 22:35
  • Optimus, out of interest, why are these values so widely used? – William Morris Nov 03 '12 at 22:54
  • They are the argument length, argument list, and shifted argument list passed to my minishell. So my main file uses them, the file that expands a line of input uses them(I had this working just by passing the variables everywhere), and now my builtin functions file use them. The problem with the builtin functions is that I execute them using structs with file pointers in them. Shift and unshift require to access these three vars, but none of the other many builtin functions need them. I don't want to have to pass them to the other functions when they won't need them. – Optimus_Pwn Nov 03 '12 at 22:58
  • 1
    @JonathanLeffler: `stdout`, `stdin`, and `stderr` are not global variables. They're not even lvalues. They're macros expanding to expressions of type `FILE *`. – R.. GitHub STOP HELPING ICE Nov 04 '12 at 00:46

5 Answers5

5

As an alternative to global variables, consider 'global functions':

extern int    msh_mainArgC(void);
extern char **msh_mainArgV(void);
extern char **msh_shiftedArgV(void);

The implementations of those functions is trivial, but it allows you control over the access to the memory. And if you need to do something fancy, you can change the implementation of the functions. (I chose to capitalize the C and V to make the difference more visible; when only the last character of an 8-12 letter name is different, it is harder to spot the difference.)

There'd be an implementation file that would define these functions. In that file, there'd be static variables storing the relevant information, and functions to set and otherwise manipulate the variables. In principle, if you slap enough const qualifiers around, you could ensure that the calling code cannot modify the data except via the functions designed to do so (or by using casts to remove the const-ness).

Whether this is worthwhile for you is debatable. But it might be. It is a more nearly 'object-oriented' style of operation. It is an alternative to consider and then discard, rather than something to leave unconsidered.

Note that your subsystems that use these functions might have one function that collects the global values, and then passes these down to its subordinate functions. This saves the subordinates from having to know where the values came from; the just operate with them correctly. If there are global variables, you have to worry about aliasing — is a function passed values (copies of the global variables) but does it also access the global variables. With the functions, you don't have to worry about that in the same way.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • A nice idea, Jonathan. Though I do not immediately follow why this is preferable to "slapping enough `const` qualifiers" around. Especially if this does not replace that act. Though, perhaps the situation in question here is not the best exemplar for this method... – Richard Nov 03 '12 at 22:42
  • The 'slapping enough `const` qualifiers around' is thinking that you might not want the code calling these functions to modify the argument lists, either the strings or the pointers to those strings. Therefore, you might try `char const * const *msh_mainArgV(void);` or something similar. At the point when I do that, I usually run into problems of some sort — I end up not being able to pass the value I've got to functions I think I should be able to pass it to. So, I'd use extreme caution before adding those qualifiers, though it would be good to know that the calling functions can't spoil 'em. – Jonathan Leffler Nov 03 '12 at 22:48
2

I would say that it is not stupid, but that you should proceed with a certain caution.

The reason globals are usually avoided is not that they should never be used, but rather that their usage has led people to frequently led programmers to crash and burn. Through experience one learns the difference between when it is the right time and when it is the wrong time.

If you have thought deeply about the problem you are trying to solve and considered the code you've wrote to solve this problem and also considered the future of this code (i.e. are you compromising maintainability) and feel that a global is either unavoidable or better represents the coded solution, then you should go with the global.

Later, you may crash and burn, but that experience will help you later discern what a better choice may have been. Conversely, if you feel as though not using the globals may lead to crashage and burnage, than this is your prior experience saying you should use them. You should trust such instincts.

Dijkstra has a paper in which he discusses the harm the goto statement may cause, but his discussion also, in my opinion, explains some of our difficulties with globals. It may be worth a read.

This answer and this answer may also be of use.

Community
  • 1
  • 1
Richard
  • 56,349
  • 34
  • 180
  • 251
  • Thanks, I have been thinking it over for hours and am going to try it. I have a bunch of function pointers that point to a certain type of function. One of these function instances now needs mainargc, mainargv, and shiftedMArgV. I don't want to have to change the prototype for the rest of the functions when they don't need these variables. These variables are used across all my files(about 6), so it makes sense to me. Thanks for the advice. – Optimus_Pwn Nov 03 '12 at 22:22
  • 1
    It sounds sensible then to treat them as globals here. Globals are much safer if you are only reading from them and not writing to them as much of the fear surrounding globals stems from the difficulty in following the program's state over time if any part of the program can affect every other part. – Richard Nov 03 '12 at 22:24
  • Incidentally, @Optimus_Pwn, it may be worth reading the paper [Gotos considered harmful](http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html) by Dijkstra, if you haven't. Many of his comments arguments surrounding the dangers of gotos also apply to globals. – Richard Nov 03 '12 at 22:26
1

Globals are ok as long as they are really globals in a logical way, and not just a mean to make your life easier. For example, globals can describe an environment in which your program executes or, in another words, attributes that are relevant on system level of your app.

SomeWittyUsername
  • 18,025
  • 3
  • 42
  • 85
  • Ah, alright. I truly think globals would be a more elegant solution in this case, because otherwise I would have to pass these variables to many functions that wouldn't even use them. – Optimus_Pwn Nov 03 '12 at 22:15
1

Pretty much all complex software I ever worked with had a set of well defined globals. There's nothing wrong with that. They range from just a handful to about a dozen. In the latter case they're usually grouped logically in structs.

Globals are usually exposed in a header file as externs, and then defined in a source file. Just remember that globals are shared between threads and thus must be protected, unless it makes more sense to declare them with thread local storage.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • I think I'm going to try implementing globals then. I should define the globals in only one of the source files if I extern them in my header file right? Thanks for the advice. – Optimus_Pwn Nov 03 '12 at 22:20
  • @Optimus_Pwn Yes, they need to be defined just once. It's not possible to do otherwise anyway, since the linker will bark when it sees them defined in multiple object files. – Nikos C. Nov 03 '12 at 22:20
  • I defined them once, in my msh.c files(The main source file). However when I try to use them in my other files(expand.c), it says expand.c:(.text+0x27c): undefined reference to `m_argc' – Optimus_Pwn Nov 03 '12 at 22:47
  • In your header file you declare them as `extern`. In the source file omit the `extern`, since you want to actually define them there, not just declare them. The `extern` keyword is similar to a forward-declaration; it just tells the compiler about the variable, it doesn't create it. Also make sure the variables aren't `static`, since that prevents them from being exported and thus the linker from seeing them. – Nikos C. Nov 03 '12 at 23:23
  • 1
    I got it, it was because I was trying to define them inside main rather than above main. I got everything working very nicely with the globals now, thanks a lot. – Optimus_Pwn Nov 04 '12 at 19:57
1

For a shell, you have a lot more state than this. You have the state of remapped file descriptors (which you need to track for various reasons), trap dispositions, set options, the environment and shell variables, ...

In general, global variables are the wrong solution. Instead, all of the state should be kept in a context structure of some sort, a pointer to which is passed around everywhere. This is good program design, and usually it allows you to have multiple instances of the same code running in the same process (e.g. multiple interpreters, multiple video decoders, etc.).

With that said, the shell is a very special case, because it also deals with a lot of global state you can't keep in a structure: signal dispositions, file descriptors and mappings, child processes, process groups, controlling terminal, etc. It may be possible to abstract a lot of this with an extra layer so that you can emulate the required behavior while keeping clean contexts that could exist in multiplicity within a single process, but that's a much more difficult task than writing a traditional shell. As such, I might give yourself some leeway to write your shell "the lazy way" using global variables. If this is a learning exercise, though, try to carefully identify each time you're introducing global variables or state, why you're doing it, and how you might be able to implement the program differently without having global state. This will be very useful to you in the future.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711