I'm currently trying to figure out a way to break out of a for
loop from within a function called in that loop. I'm aware of the possibility to just have the function return a value and then check against a particular value and then break, but I'd like to do it from within the function directly.
This is because I'm using an in-house library for a specific piece of hardware that mandates the function signature of my function to look like this:
void foo (int passV, int aVal, long bVal)
I'm aware that not using a return value is very bad practice, but alas circumstances force me to, so please bear with me.
Consider following example:
#include <stdio.h>
void foo (int a) {
printf("a: %d", a);
break;
}
int main(void) {
for (int i = 0; i <= 100; i++) {
foo(i);
}
return 0;
}
Now this does not compile. Instead, I get a compilation error as follows:
prog.c: In function 'foo': prog.c:6:2: error: break statement not within loop or switch break;
I know what this means (the compiler says that the break in foo()
is not within a for
loop)
Now, what I could find from the standard regarding the break statement is this:
The break statement causes control to pass to the statement following the innermost enclosing while, do, for, or switch statement. The syntax is simply break;
Considering my function is called from within a for
loop, why doesn't the break statement break out of said for loop? Furthermore, is it possible to realise something like this without having the function return first?
break
, like goto
, can only jump locally within the same function, but if you absolutely have to, you can use setjmp
and longjmp
:
#include <stdio.h>
#include <setjmp.h>
jmp_buf jump_target;
void foo(void)
{
printf("Inside foo!\n");
longjmp(jump_target, 1);
printf("Still inside foo!\n");
}
int main(void) {
if (setjmp(jump_target) == 0)
foo();
else
printf("Jumped out!\n");
return 0;
}
The call to longjmp
will cause a jump back to the setjmp
call. The return value from setjmp
shows if it is returning after setting the jump target, or if it is returning from a jump.
Output:
Inside foo!
Jumped out!
Nonlocal jumps are safe when used correctly, but there are a number of things to think carefully about:
- Since
longjmp
jumps "through" all the function activations between thesetjmp
call and thelongjmp
call, if any of those functions expect to be able to do additional work after the current place in execution, that work will simply not be done. - If the function activation that called
setjmp
has terminated, the behaviour is undefined. Anything can happen. - If
setjmp
hasn't yet been called, thenjump_target
is not set, and the behaviour is undefined. - Local variables in the function that called
setjmp
can under certain conditions have undefined values. - One word: threads.
- Other things, such as that floating-point status flags might not be retained, and that there are restrictions on where you can put the
setjmp
call.
Most of these follow naturally if you have a good understanding of what a nonlocal jump does on the level of machine instructions and CPU registers, but unless you have that, and have read what the C standard does and does not guarantee, I would advise some caution.
You cannot use break;
this way, it must appear inside the body of the for
loop.
There are several ways to do this, but neither is recommended:
you can exit the program with the
exit()
function. Since the loop is run frommain()
and you do not do anything after it, it is possible to achieve what you want this way, but it as a special case.You can set a global variable in the function and test that in the
for
loop after the function call. Using global variables is generally not recommended practice.you can use
setjmp()
andlongjmp()
, but it is like trying to squash a fly with a hammer, you may break other things and miss the fly altogether. I would not recommend this approach. Furthermore, it requires ajmpbuf
that you will have to pass to the function or access as a global variable.
An acceptable alternative is to pass the address of a status
variable as an extra argument: the function can set it to indicate the need to break from the loop.
But by far the best approach in C is returning a value to test for continuation, it is the most readable.
From your explanations, you don't have the source code for foo()
but can detect some conditions in a function that you can modify called directly or indirectly by foo()
: longjmp()
will jump from its location, deep inside the internals of foo()
, possibly many levels down the call stack, to the setjmp()
location, bypassing regular function exit code for all intermediary calls. If that's precisely what you need to do to avoid a crash, setjmp()
/ longjmp()
is a solution, but it may cause other problems such as resource leakage, missing initialization, inconsistent state and other sources of undefined behavior.
Note that your for
loop will iterate 101
times because you use the <=
operator. The idiomatic for
loop uses for (int i = 0; i < 100; i++)
to iterate exactly the number of times that appears as the upper (excluded) bound.
(Note: the question has been edited since I originally wrote this)
Because of the way C is compiled it must know where to break to when the function is called. Since you can call it from anywhere, or even somewhere a break makes no sense, you cannot have a break;
statement in your function and have it work like this.
Other answers have suggested terrible solutions such as setting a global variable, using a #define
or longjumping(!) out of the function. These are extremely poor solutions. Instead, you should use the solution you wrongly dismiss in your opening paragraph and return a value from your function that indicates the state that you want to trigger a break
in this case and do something like this:
#include <stdbool.h>
bool checkAndDisplay(int n)
{
printf("%d\n", n);
return (n == 14);
}
int main(void) {
for (int i = 0; i <= 100; i++) {
if (checkAndDisplay(i))
break;
}
return 0;
}
Trying to find obscure ways to achieve things like this instead of using the correct way to achieve the same end result is a surefire way to generate dire quality code that is a nightmare to maintain and debug.
You mention, hidden in a comment, that you must use a void return, this is not a problem, just pass the break parameter in as a pointer:
#include <stdbool.h>
void checkAndDisplay(int n, bool* wantBreak)
{
printf("%d\n", n);
if (n == 14)
wantBreak = true;
}
int main(void) {
bool wantBreak = false;
for (int i = 0; i <= 100; i++) {
checkAndDisplay(i, &wantBreak);
if (wantBreak)
break;
}
return 0;
}
Since your parameters are fixed type I suggest you use a cast to pass in the pointer to one of the parameters, e.g. foo(a, b, (long)&out);
break
is statement which is resolved during compile time. Therefore the compiler must find appropriate for/while loop within the same function. Note that there is no guarantee that the function couldn't be called from somewhere else.
If you cannot use the break
instruction you could define a local variable in your module and add a second run condition to the for
loop. For example like the following code:
#include <stdio.h>
#include <stdbool.h>
static bool continueLoop = true;
void foo (int a)
{
bool doBreak = true;
printf("a: %d",a);
if(doBreak == true){
continueLoop = false;
}
else {
continueLoop = true;
}
}
int main(void) {
continueLoop = true; // Has to be true before entering the loop
for (int i = 0; (i <= 100) && continueLoop; i++)
{
foo(i);
}
return 0;
}
Note that in this example this is not exactly a break
-instruction, but the for
loop will not do another iteration. If you want to do a break
you have to insert an if
-condition with the variable continueLoop
which leads to break
:
int main(void) {
continueLoop = true; // Has to be true before entering the loop
for (int i = 0; i <= 100; i++)
{
foo(i);
if(!continueLoop){
break;
}
}
return 0;
}
I believe it's related to how a break
statement is translated into machine code. The break
statement will be translated as a unconditional branch to the label immediately following the loop or switch.
mov ECX,5
label1:
jmp <to next instruction address> ;break
loop label1
<next instruction>
While the call to foo()
from inside the loop will result in something like
mov ECX,5
label1:
call <foo address>
loop label1
<next instruction>
and at foo
address
call <printf address>
jmp <to where?> ;break cannot translate to any address.
This is another idea that may or may not be feasible: keep a variable around that can turn foo into a no-op:
int broken = 0;
void foo (int a) {
if (broken) return;
printf("a: %d", a);
broken = 1; // "break"
}
int main(void) {
for (int i = 0; i <= 100; i++) {
foo(i);
}
return 0;
}
This is functionally the same except for some loss of clock cycles (the function will be called, but perform only the if
statement), and there is no need to change the loop. It's not threadsafe and only works the first time (but foo
could reset the broken
variable to 0 if called with a
equal to 0, if needed).
So not great, but an idea that wasn't mentioned yet.
In a case like this consider using a while() loop with several conditional statements chained with && instead of a for loop. Although you can alter the normal control flow using functions like setjmp and longjmp, it's pretty much considered bad practice everywhere. You shouldn't have to search too hard on this site to find out why. ( In short it's because of it's capacity to create convoluted control flow that doesn't lend itself to either debugging or human comprehension )
Also consider doing something like this:
int foo (int a) {
if(a == someCondition) return 0;
else {
printf("a: %d", a);
return 1;
}
}
int main(void) {
for (int i = 0; i <= 100; i++) {
if(!foo(i)) break;
}
return 0;
}
In this case, the loop depends on a true value being returned from 'foo', which will break the loop if the condition inside 'foo' is not met.
Edit: I'm not explicitly against the use of goto, setjmp, longjmp etc. But I think in this case there is a much simpler and more concise solution available without resorting to these measures!
If you cannot handle return values, can you at least add a Parameter to the function: I can imagine a solution like that:
void main (void)
{
int a = 0;
for (; 1 != a;)
{
foo(x, &a);
}
}
void foo( int x, int * a)
{
if (succeeded)
{
/* set the break condition*/
*a = 1;
}
else
{
*a = 0;
}
}
It's my first post, so, please forgive me, if my formatting is messed up :)
Following your updated question clearly setting out the limitations, I would suggest you move the entire loop inside your function and then call a second function with a return value inside that function, e.g.
#include <stdbool.h>
bool foo (int x)
{
return (x==14);
}
void loopFoo(int passV, int aVal, long bVal)
{
for (int i = 0; i <= 100; ++i)
{
if (foo(x))
break;
}
}
This avoids any extreme and fragile gymnastics to get around the limitation.
Just set a global variable and check that on the loop:
#include <stdio.h>
int leave = 0;
void foo (int a) {
printf("a: %d", a);
leave = 1;
}
int main(void) {
for (int i = 0; i <= 100; i++) {
foo(i);
if (leave)
break;
}
return 0;
}
Consider inlining your function manually in the for
loop. If this function is called in multiple loops, define it as a macro:
#define f()\
printf("a: %d", a);\
break;
You can throw an error in your function inside the loop and catch that error outside the loop.
#include <stdio.h>
void foo (int a) {
printf("a: %d", a);
if (a == 50)
{
throw a;
}
}
int main(void) {
try {
for (int i = 0; i <= 100; i++) {
foo(i);
}
catch(int e) {
}
return 0;
}
This question has already been answered, but I think it is worth delving into all of the possible options to exit a loop in c++. There are basically five possibilities:
- Using a loop condition
- Using a
break
condition - Using a
return
condition - Using exceptions
- Using
goto
In the following, I will describe use cases for these options using c++14. However, you can do all of these in earlier versions of c++ (except maybe exceptions). To keep it short, I will omit the includes and the main function. Please comment, if you think some part needs more clarity.
1. Using a loop condition
The standard way to exit a loop is a loop condition. The loop condition is written in the middle part of a for
statement, or between the parentheses of a while
statement:
for(something; LOOP CONDITION; something) {
...
}
while (LOOP CONDITION)
...
}
do {
...
} while (LOOP CONDITION);
The loop condition decides if the loop should be entered, and if the loop should be repeated. In all of the above cases, the condition has to be true
, for the loop to be repeated.
As an example, if we want to output the number from 0 to 2, we could write the code using a loop and a loop condition:
for (auto i = 0; i <= 2; ++i)
std::cout << i << '\n';
std::cout << "done";
Here the condition is i <= 2
. As long as this condition evaluates to true
, the loop keeps running.
An alternative implementation would be to put the condition into a variable instead:
auto condition = false;
for (auto i = 0; !condition; ++i) {
std::cout << i << '\n';
condition = i > 2;
}
std::cout << "done";
Checking the output for both versions, we get the desired result:
0
1
2
done
How would you use a loop condition in an real world application?
Both versions are widely used inside c++ projects. It is important to note, that the first version is more compact and therefore easier to understand. But the second version is usually used if the condition is more complex or needs several steps to be evaluated.
For example:
auto condition = false;
for (auto i = 0; !condition; ++i)
if (is_prime(i))
if (is_large_enough(i)) {
key = calculate_cryptographic_key(i, data);
if (is_good_cryptographic_key(key))
condition = true;
}
2. Using a break
condition
Another simple way to exit a loop, is to use the break
keyword. If it is used inside the loop, the execution will stop, and continue after the loop body:
for (auto i = 0; true; ++i) {
if (i == 3)
break;
std::cout << i << '\n';
}
std::cout << "done";
This will output the current number, and increment it by one, until i
reaches a value of 3. Here the if
statement is our break
condition. If the condition is true
, the loop is broken (note the !
) and the execution continues with the next line, printing done
.
Doing the test, we indeed get the expected result:
0
1
2
done
It is important, that this will only stop the innermost loop in the code. Therefore, if you use multiple loops, it can lead to undesired behaviour:
for (auto j = 0; true; ++j)
for (auto i = 0; true; ++i) {
if (i == 3)
break;
std::cout << i << '\n';
}
std::cout << "done";
With this code we wanted to get the same result as in the example above, but instead we get an infinite loop, because the break
only stops the loop over i
, and not the one over j
!
Doing the test:
0
1
2
0
1
2
...
How would you use a break
condition in an real world application?
Usually break
is only used to skip parts of an inner loop, or to add an additional loop exit.
For example, in a function testing for prime numbers, you would use it to skip the rest of the execution, as soon as you found a case where the current number is not prime:
auto is_prime = true;
for (auto i = 0; i < p; ++i) {
if (p%i == 0) { //p is dividable by i!
is_prime = false;
break; //we already know that p is not prime, therefore we do not need to test more cases!
}
Or, if you are searching a vector of strings, you usually put the maximum size of the data in the loop head, and use an additional condition to exit the loop if you actually found the data you are searching for.
auto j = size_t(0);
for (auto i = size_t(0); i < data.size(); ++i)
if (data[i] == "Hello") { //we found "Hello"!
j = i;
break; //we already found the string, no need to search any further!
}
3. Using a return
condition
The return
keyword exits the current scope and returns to the calling function. Thus it can be used to exit loops, and in addition, give back a number to the caller. A common case is to use return
to exit a loop (and its function) and return a result.
For example, we can rewrite the is_prime
function from above:
auto inline is_prime(int p) {
for (auto i = 0; i < p; ++i)
if (p%i == 0) //p is dividable by i!
return false; //we already know that p is not prime, and can skip the rest of the cases and return the result
return true; //we didn't find any divisor before, thus p must be prime!
}
The return
keyword can also be used to exit multiple loops:
auto inline data_has_match(std::vector<std::string> a, std::vector<std::string> b) {
for (auto i = size_t(0); i < a.size(); ++i)
for (auto j = size_t(0); j < a.size(); ++j)
if (a[i] == b[j])
return true; //we found a match! nothing to do here
return false; //no match was found
}
How would you use a return
condition in an real world application?
Inside smaller functions, return
is often used to exit loops and directly return results. Furthermore, inside larger functions, return
helps to keep the code clear and readable:
for (auto i = 0; i < data.size(); ++i) {
//do some calculations on the data using only i and put them inside result
if (is_match(result,test))
return result;
for (auto j = 0; j < i; ++j) {
//do some calculations on the data using i and j and put them inside result
if (is_match(result,test))
return result;
}
}
return 0; //we need to return something in the case that no match was found
Which is much easier to understand, than:
auto break_i_loop = false;
auto return_value = 0;
for (auto i = 0; !break_i_loop; ++i) {
//do some calculations on the data using only i and put them inside result
if (is_match(result,test)) { //a match was found, save the result and break the loop!
return_value = result;
break;
}
for (auto j = 0; j < i; ++j) {
//do some calculations on the data using i and j and put them inside result
if (is_match(result,test)) { //a match was found, save the result, break the loop, and make sure that we break the outer loop too!
return_value = result;
break_i_loop = true;
break;
}
}
if (!break_i_loop) //if we didn't find a match, but reached the end of the data, we need to break the outer loop
break_i_loop = i >= data.size();
}
return return_value; //return the result
4. Using exceptions
Exceptions are a way to mark exceptional events in your code. For example, if you want to read data from a file, but for some reason the file doesn't exist! Exceptions can be used to exit loops, however the compiler usually generates a lot of boilerplate code to safely continue the program if the exception is handled. Therefore exceptions shouldn't be used to return values, because it is very inefficient.
How would you use an exception in an real world application?
Exceptions are used to handle truly exceptional cases. For example, if we want to calculate the inverse of our data, it might happen that we try to divide by zero. However this is not helpful in our calculation, therefore we write:
auto inline inverse_data(std::vector<int>& data) {
for (auto i = size_t(0); i < data.size(); ++i)
if (data[i] == 0)
throw std::string("Division by zero on element ") + std::to_string(i) + "!";
else
data[i] = 1 / data[i];
}
We can the handle this exception inside the calling function:
while (true)
try {
auto data = get_user_input();
inverse = inverse_data(data);
break;
}
catch (...) {
std::cout << "Please do not put zeros into the data!";
}
If data
contains zero, then inverse_data
will throw an exception, the break
is never executed, and the user has to input data again.
There are even more advance options for this kind of error handling, with additional error types, ..., but this is a topic for another day.
** What you should never do! **
As mentioned before, exceptions can produce a significant runtime overhead. Therefore, they should only be used in truly exceptional cases. Although it is possible to write the following function, please don't!
auto inline next_prime(int start) {
auto p = start;
try {
for (auto i = start; true; ++i)
if (is_prime(i)) {
p = i;
throw;
}
}
catch (...) {}
return p;
}
5. Using goto
The goto
keyword is hated by most programmers, because it makes code harder to read, and it can have unintended side effects. However, it can be used to exit (multiple) loops:
for (auto j = 0; true; ++j)
for (auto i = 0; true; ++i) {
if (i == 3)
goto endloop;
std::cout << i << '\n';
}
endloop:
std::cout << "done";
This loop will end (not like the loop in part 2), and output:
0
1
2
done
How would you use a goto
in an real world application?
In 99.9% of the cases there is no need to use the goto
keyword. The only exceptions are embedded systems, like an Arduino, or very high performance code. If you are working with one of these two, you might want to use goto
to produce faster or more efficient code. However, for the everyday programmer, the downsides are much bigger than the gains of using goto
.
Even if you think your case is one of the 0.1%, you need to check if the goto
actually improves your execution. More often than not, using a break
or return
condition is faster, because the compiler has a harder time understanding code containing goto
.
来源:https://stackoverflow.com/questions/35404220/breaking-out-of-a-loop-from-within-a-function-called-in-that-loop