1

I am trying to exit a program without using exit(). I have come up with a very convoluted and dirty solution (I am a Beginner). I would like to use if statements and if it is true, then I would like to use goto to go the main function and then return 3; and end the program.

Here is a bit of code:

    FILE *filepointer;

      char * line = NULL;
      size_t len = 0;
      size_t read;
      int linecount = 0;
      filepointer = fopen(filename, "r");
      if (filepointer == NULL)
        {
          printf("[ERR] Could not read file %s.\n",filename );
          goto FILE_ERROR;
        }
    ...
    int main(){
    ...
    FILE_ERROR: return 3;

}

This however does not work as I cannot get jump between functions because I get undeclared Label as an error. Is there any way I can exclude exit() from my program and still end it returning a certain value. If there is a better solution, please let me know

midor
  • 5,487
  • 2
  • 23
  • 52

4 Answers4

4

The only good answer to this question is: don't do it. gotos used in this way make your code very hard to reason about.

Refactor your code so that you have a clear structure and hierarchy of calls. Use return values to propagate success/failure throughout the call stack.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
2

goto can't be used to jump across different functions; it can only be used within the same function. To jump between functions, you can look at setjmp() and longjmp() functions.

Having said, since you claim to be a beginner, I am not convinced you really need to use either of the above. You can simply modify your function to return an "error" value. And in main(), check its value and return from main() with the error value.

P.P
  • 117,907
  • 20
  • 175
  • 238
  • 3
    Please do not look at `setjmp()` and `longjmp()` until you understand how to properly structure your code. – Vittorio Romeo Dec 06 '17 at 10:07
  • I am not exactly a follower of carg-cult programming. That's why I mentioned it *can be* done. But it's not really OP you needed. – P.P Dec 06 '17 at 10:15
  • @VittorioRomeo Please do not look at `setjmp()` and `longjmp()` **ever**. These two functions were implemented and defined in the very beginning of C because they were easy to implement and promised to be powerful. An assessment of their utility and their danger was not done. It is only later that people realized that their danger far outweights their utility. They are much worse than a `goto`. But people didn't realize that until after the damage was done and these two functions were de-facto part of the C standard. – cmaster - reinstate monica Dec 06 '17 at 10:18
  • 1
    @cmaster: The “never” is terrible advice. There are legitimate uses for `setjmp` and `longjmp`, even if there are many associated hazards. Unlike, for example, `gets` which is far less safe than `longjmp`. – Dietrich Epp Dec 06 '17 at 15:27
  • @DietrichEpp Can you name some such uses? I only know of the use as an exception replacement/implementation, and that use is definitely most deeply flawed when any hand-written C code in involved. (The exception is that the entire C code is only the output of some other compiler frontend that inserts both the `setjmp()`/`longjmp()` calls and the necessary cleanup code to implement exceptions in the high-level language. And even in that case, `setjmp()`/`longjmp()` is a poor implementation choice.) So, I fail to see a valid use case, but if you have one, I'd be happy to hear about it :-) – cmaster - reinstate monica Dec 06 '17 at 15:49
  • @cmaster Yes, exception handling is the most common use case. But why do you think it's "deeply flawed"? What else do you propose, say when returning from a SIGSEGV handling code, that cuts across multiple stack frames for example? – P.P Dec 06 '17 at 15:57
  • @cmaster In larger programs or in general usage it is certainly dangerous and difficult to use correctly. However, C is often used to implement the runtime for other languages, for example, in Lua where a longjmp greatly simplifies error handling. C is also used for low-level systems programming where you may occasionally want to do something like `longjmp` out of a signal handler, which is ordinary very dangerous but in certain cases is useful. In particular, it is the ONLY way to recover from SIGBUS or SIGSEGV, so in those cases it is irreplaceable. – Dietrich Epp Dec 06 '17 at 16:00
  • @cmaster The exception handling case is not always bad either. There are cases where you have a high confidence that `longjmp` is safe, so branding it a “poor implementation choice” is a bit too general. It’s like dynamite … most people shouldn’t have sticks of dynamite in their toolbox, but some of us are glad it exists. – Dietrich Epp Dec 06 '17 at 16:08
  • @DietrichEpp Ok, if you must, recover from these signals, then you may be forced to use `longjmp()`. The danger that I see, is that operations are skipped in an entirely uncontrolled way. When you throw an exception in C++, it's the compilers duty to make sure that all destructors get called. There is no such thing with `longjmp()`. As such, I see the SIGBUS and SIGSEGV signals simply as unrecoverable signals, that force a quick uncheatable death - there's nothing that can safely be recovered. You are allowed to use the handler to print some diagnostics, but that's it, after that you die. – cmaster - reinstate monica Dec 06 '17 at 20:18
  • I likel your dynamite analogy, though (IMHO, nitroglycerin would be a better analogy, see https://en.wikipedia.org/wiki/Nitroglycerin), we may only differ in our assessment of the environment: When I think of any sizable piece of code, I think of tons of dirt, impurities, rough handling, blatant mistakes, and smoking employees to set of the nitroglycerin... The only way that I can think of controlling the danger is to remove the human factor altogether. Or remove the nitroglycerin. – cmaster - reinstate monica Dec 06 '17 at 20:19
  • @cmaster: You say that “these operations are skipped in an entirely uncontrolled way” but that’s simply not always true, and it’s just plain false that SIGSEGV is unrecoverable. Don’t think of this from the perspective of an application developer, where the idea of recovering from an arbitrary SIGSEGV “somewhere” in your application just means that your application contains a memory error. Instead, consider this from a systems programmer perspective. Is it possible to write code where SIGSEGV is expected, normal, and safe? The answer is absolutely and unequivocally “yes”. – Dietrich Epp Dec 07 '17 at 07:18
  • @DietrichEpp If you are not in user space, you are in kernel space, and kernels don't get signals, afaik. Kernels get interrupts instead. IMHO, if the interrupt handler for an address space violation identifies a user space process as the source, it should shoot that process. If the source is a kernel thread, it should panic. However, it's true that I'm more of a user space programmer, so there may indeed be cases where an interrupt handler for an address space violation must be able to resume the violating kernel thread. I guess, there are quite insane hardware interfaces out there... – cmaster - reinstate monica Dec 07 '17 at 12:33
  • 1
    With careful programming, SIGSEGV is recoverable from userspace itself without involving "uncontrollable ways". In short, there are genuine use-cases for setjmp/longjmp even in userspace programs. – P.P Dec 07 '17 at 12:46
  • @cmaster: I wasn't even thinking about kernels, all of the examples I was thinking of were from ordinary userspace programs. Kernel programming is just one type of systems programming. – Dietrich Epp Dec 07 '17 at 15:13
  • @DietrichEpp That puzzles me. Under what circumstances would you *want* to recover a user-space process from a SIGSEGV? Can you give an example? – cmaster - reinstate monica Dec 07 '17 at 15:43
  • @cmaster: When writing a garbage collector, you can make a page fault in the allocator trigger collection. In a database, you can make a page fault load from disk. In an interpreter, you can make a page fault generate a null pointer exception. If you are writing a parser, you can use `longjmp` to return an error from a deeply nested function. The `longjmp` function is a very powerful and useful tool, it’s just also dangerous and often inappropriate. – Dietrich Epp Dec 07 '17 at 16:55
  • @DietrichEpp Garbage Collection: So you want the allocator to routinely invoke UB to save a single comparison per allocation? I'd think, there must be better ways. Database: If you want lazy loading, `mmap()` the file/disk. Interpreter: This has some justification. Parser: I would throw you off my project if you did this. A parser exists to build an AST, and if you abort its creation in the middle and happily resume work, you are left with a ton of half constructed objects. That classifies as deeply broken code IMNSHO. But, as I said, the interpreter use-case is a valid one. Thanks :-) – cmaster - reinstate monica Dec 07 '17 at 17:19
  • 1
    @cmaster: A SIGSEGV is not actually undefined behavior. It sounds like you have very strong opinions on this subject, but I honestly am a bit hurt by some of these responses like “I would throw you off my project if you did this” or “deeply broken code”. Is that an appropriate comment? It also sounds like there are a large number of assumptions here, like about half-constructed objects. If you are allocating those objects in a pool, which is not unusual, you can simply free the pool after the `longjmp`. There is a great variety of coding styles, coding styles you don’t like are not “wrong”. – Dietrich Epp Dec 07 '17 at 17:36
  • @cmaster: “Saving a single comparison” in the allocator can actually be an enormous win because it can bring heap allocation much closer to stack allocation in terms of performance in certain situations and using certain metrics. – Dietrich Epp Dec 07 '17 at 17:39
  • @DietrichEpp You cannot get a SIGSEGV without invoking UB first: It means that you have an access outside of a memory object allocated via some system function (`mmap`, `malloc`, etc.). I.e. you are dereferencing an invalid pointer by the definition of the C language standard. Anyway, sorry that I hurt your feelings. I tried to be very clear about my point, and I tried to explain my reasoning. Yes, I have strong opinions about coding - it's what I love to do, after all. I see the pool strategy as a valid damage encapsulation technique, but I'd rather avoid the damage in the first place. – cmaster - reinstate monica Dec 07 '17 at 18:55
  • @cmaster: I feel like a bit of a broken record here, but it is an outright falsehood to say that only undefined behavior triggers SIGSEGV. Please refer to the [`mmap` man page](http://man7.org/linux/man-pages/man2/mmap.2.html) and note the `prot` argument. It's good to have opinions but I expect people to acknowledge that their opinions are not absolute verdicts on the matter. – Dietrich Epp Dec 07 '17 at 21:35
  • @cmaster: Also note that `mmap` is most commonly available on Posix systems or systems which implement similar interfaces, and the Posix standard requires certain behavior beyond what the C standard requires. So SIGSEGV can still be portable, because a Posix-compliant C compiler must make it so. – Dietrich Epp Dec 07 '17 at 21:38
  • @DietrichEpp Good point about the `prot` argument, I totally missed that. So, I take back what I said about SIGSEGV and UB, and claim the opposite :-) Sorry for claiming wrong stuff, and thanks for your patience correcting me. – cmaster - reinstate monica Dec 07 '17 at 22:23
0

By design, a goto cannot jump from one function to another. It can only be used to jump within a given function.

There are ways to jump between functions, but doing so is not only very poor design but also dangerous as it is very easy to put your program in an invalid state.

The proper way to handle this is to have your function return a specific value (or set of values) to indicate an error. Then the calling function would check for one of those error values and act accordingly.

For example:

int readFile(char *filename)
{
  FILE *filepointer;

  char * line = NULL;
  size_t len = 0;
  size_t read;
  int linecount = 0;
  filepointer = fopen(filename, "r");
  if (filepointer == NULL)
    {
      // add strerror(error) to the error message to know why fopen failed
      printf("[ERR] Could not read file %s: %s.\n",filename, strerror(errno) );
      // error completion
      return 0;
    }
  ...
  // successful completion
  return 1;
}

int main(){
   ...
   if (readFile("myfile") == 0) {
       return 3;
   }
   ...
}
dbush
  • 205,898
  • 23
  • 218
  • 273
0

If you wanted to use a go-to , and insisted on doing that, you could I guess try to expand your 1st function so it includes / encapsulates the 2nd function, and get rid of the 2nd function conpletely, so your able to do go tos and subroutines within this much larger function.

Is that an option you could try ? (If you were dead cert on using Goto's, ) ?

I would give that a go.

David Wooley - AST
  • 346
  • 2
  • 4
  • 13