Я читаю исходный код реализации протопотока в Contiki OS, который разработан Адамом Данкелсом из SICS, Швеция. И меня действительно смущает одно небольшое различие между его реализацией и идеей совместных подпрограмм, продемонстрированной Саймон Тэтхэм - поэтому переменная состояния не должна быть статической в Реализация протопотока Адама, хотя в статье Саймона объявлена статической?
Давайте сначала внимательно посмотрим на обсуждение Саймона. Например, было бы неплохо написать функцию, которая говорит
int function(void) {
int i;
for(i=0; i<10; i++)
return i; //actually won't work in C
}
и десять последовательных вызовов функции возвращают числа от 0 до 9.
Этого можно добиться, используя в этой функции следующие макросы:
#define crBegin static int state=0; switch(state) { case 0:
#define crReturn(i,x) do { state=__LINE__; return x; \
case __LINE__:; } while (0)
#define crFinish }
int function(void) {
static int i;
crBegin;
for (i = 0; i < 10; i++)
crReturn(1, i);
crFinish;
}
Вызов этой функции десять раз даст, как и ожидалось, от 0 до 9.
К сожалению, это не сработает, если мы будем использовать локальные макросы продолжения Адама в оболочке switch-case, как это (/core/sys/lc-switch.h в дереве Contiki src), даже если вы сделаете переменную состояния статической:
typedef unsigned short lc_t;
#define LC_INIT(s) s = 0; // the ";" must be a mistake...
#define LC_RESUME(s) switch(s) { case 0:
#define LC_SET(s) s = __LINE__; case __LINE__:
#define LC_END(s) }
int function(void) {
static int i;
lc_t s;
LC_INIT(s);
LC_RESUME(s);
for (i = 0; i < 10; i++)
{ return i;
LC_SET(s);
}
LC_END(s);
}
Здесь, как в примере Саймона, s работает как переменная состояния, которая сохраняет позицию (предел текучести), установленную LC_SET (s). И когда функция позже возобновит выполнение (с начала), она переключится в соответствии со значением s. Такое поведение дает эффект, что функция продолжает работать после позиции yield, установленной предыдущим вызовом.
Различия между этими двумя наборами макросов:
- переменная состояния s статична в примере Саймона, но нестатична в определении Адама LC;
- crReturn устанавливает состояние прямо перед возвратом результата, в то время как в определении Адама LC LC_SET (s) просто устанавливает состояние и отмечает предел текучести.
Конечно, последнее не будет работать с этим случаем цикла for в функции. Ключ к такому поведению «возврат и продолжение» заключается как в статической переменной состояния, так и в том, что состояние устанавливается прямо перед оператором return. Очевидно, макросы LC не соответствуют ни одному из требований. Так почему же макросы LC устроены именно так?
Все, что я могу предположить прямо сейчас, это то, что эти макросы LC являются примитивами очень низкого уровня и не должны использоваться так, как показано в этом примере цикла for. Нам необходимо создать эти макросы PT, обернутые этими примитивами LC, чтобы сделать их действительно полезными. И макрос crReturn предназначен только для демонстрационных целей, чтобы точно соответствовать случаю цикла for, поскольку не каждый раз вы хотите, чтобы выполнение выполнялось возвратом из функции.