Implementing yield in C
Asked Answered
T

3

15

For example:

int getNext(int n) {
    while (TRUE) {
        n = n+1;
        yield n;
    }
}

int main() {
    while (TRUE) {
        int n = getNext(1);
        if (n > 42)
           break;
        printf("%d\n",n);
    }
}

Such that the above code would print all numbers from 1 to 42. I thought of making yield change the address of getNext to the instruction after yield. But I cant figure out how I would save the context (registers/variables) since the stack would be ran over by the caller function.

Note:

I realize that the code above can be easily implemented by static variables, but that's not the point.

Trefor answered 4/7, 2013 at 21:24 Comment(10)
However you do it, you won't be able to do it with the syntactic ease of C# (or whatever). This is what context variables and function pointers are for.Bacteriostat
@Bacteriostat This is pure academic, just wondering what's the general idea.Trefor
Have a look at <ucontext.h> maybe.Octagon
In general, yield style iterators need some restructuring of the code to turn it a state machine (unless you want to play stupid tricks with the stack, that is). Are you only interested in simple cases like the one in the question, where that isn't a concern?Poppyhead
Or perhaps Boost.Coroutine already has something useful, if you want to write in C++ (or just look at the source to learn some tricks).Octagon
@Trefor If I was going to do solve the problem that your example is trying to solve, I'd follow the iterator design pattern. I understand it's an academic question about yield. I don't think you're going to be able to end up with a construct that you can use in the style of yield. I'd love to be proved wrong.Bacteriostat
If you want a state machine, you need a state variable - which is what a static variable is ideal for (static - state. It's even in the name). But you don't want to use a static...Dispatcher
@delnan Actually playing with the stack interests me. Maybe even keeping a different stack for functions that use yieldTrefor
Please let us know when you find an answer! More than one person would like to know.Bacteriostat
@Dispatcher The problem with a static variable is that you can't have more than one generator running at the same time (and if you want to restart it, things get ugly). It's conceptually awful, and in practice it only works for simple use cases.Poppyhead
W
19

You can do so, even in portable C. Here is a crude example that works (gcc -Wall gen.c):

#include <stdbool.h>
#include <stdio.h>
#include <setjmp.h>

#define YIELD(func, n) if (! setjmp(func##_gen_jmp)) {  \
      func##_ret = n;                                   \
         longjmp(func##_caller_jmp, 1);                 \
  }


#define GENERATOR(ret, func, argt, argv)        \
  static jmp_buf func##_caller_jmp;             \
  static jmp_buf func##_gen_jmp;                \
  static bool func##_continue=false;            \
  static ret func##_ret;                        \
                                                \
  void func##__real(argt argv);                 \
                                                \
  ret func(argt argv) {                         \
    if (!func##_continue) {                     \
    func##_continue=true ;                      \
      if (! setjmp(func##_caller_jmp)) {        \
        func##__real(argv);                     \
      } else {                                  \
        return func##_ret;                      \
      }                                         \
    }                                           \
     else {                                     \
      longjmp(func##_gen_jmp,1);                \
    }                                           \
    return 0;                                   \
  }                                             \
  void func##__real(argt argv)



GENERATOR(int, getNext, int, n) {
  static int counter;

  counter = n;
    while (true) {
        counter = counter+1;
        YIELD(getNext, counter);
    }
}

int main() {
    while (true) {
      int n = getNext(1);
        if (n > 42)
           break;
        printf("%d\n",n);
    }
    return 0;
}
Willard answered 5/7, 2013 at 7:52 Comment(2)
This prints 2 and then rashes on for me.Rebec
It works with gcc but does not work with cl.exeGlut
S
2

What you're looking for is called "coroutines" and (at least not without loss of generality—see the other answer for a method for doing it in limited cases) is not possible in portable C. There are a number of tricks by which you can fake them; see http://en.wikipedia.org/wiki/Coroutine#Implementations_for_C for several.

Scrounge answered 5/7, 2013 at 1:0 Comment(0)
R
0

Here is a short version of something that was posted here a while back, but I can't find the original question or answer. I have reduced it to the shortest code that I can, and have added & changed comments. It generates squares from a range of numbers:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int sq_gen (int a, int b);

int sq_gen (int a, int b) {
    static int i, resume = 0;
    if (resume) goto resume; else resume = 1;
    printf("Initializing, start = %d, end = %d\n", a, b);
    for (i = a; i <= b; i += 1) {
        printf("Looping, counter = %d\n", i);
        return i * i;
        resume:;
    }
    resume = 0;
    return 0;
}

int main(int argc, char **argv) {
    int square;

    assert(argc == 3);
    while ((square = sq_gen((int)strtol(argv[1], NULL, 10), (int)strtol(argv[2], NULL, 10)))) {
        printf("main() square = %d\n", square);
    }

    return 0;
}

NOTE: start and end values are now command arguments.

Rocco answered 9/7, 2022 at 15:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.