Я придумал код, который использует самореферентную структуру (первый элемент структуры — это указатель на функцию, которая принимает экземпляр структуры в качестве своего единственного аргумента).
Было полезно передавать разрозненные подпрограммы другой для вызова, потому что вызывающей подпрограмме не нужно знать точный состав аргументов переданных подпрограмм (см. process_string
места вызова в коде ниже). Сами переданные/вызванные подпрограммы отвечают за распаковку (приведение) аргументов осмысленным для них способом.
В нижней части этого поста приведен пример кода, использующего эту технику. При компиляции с gcc -std=c99 -Wpedantic -Wall -Wextra -Wconversion
он производит следующий вывод:
nread: 5
vals[0]: 0.000000
vals[1]: 0.000000
vals[2]: 0.000000
vals[3]: 78.900000
vals[4]: 32.100000
vals[5]: 65.400000
vals[6]: 87.400000
vals[7]: 65.000000
12.3 12.3
34.5 34.5
56.7 56.7
78.9 78.9
32.1 32.1
65.4 65.4
87.4 87.4
65.0 65.0
Мои вопросы:
- Как называется эта техника? Как видно из кода, я использовал имя функтор, но я не уверен, что это правильно. Это немного похоже на замыкание, но я не думаю, что это так, поскольку оно просто указывает на свои аргументы, а не копирует их.
- Нарушает ли код строгое правило псевдонимов?
- Вызывает ли код Undefined Behavior?
А теперь по коду:
#include <stdio.h>
typedef struct functor_s functor_t;
typedef int (func_t)(functor_t);
struct functor_s { func_t * _0; void * _1; void * _2; void * _3; void * _4; };
void process_string(char * buf, int skip, functor_t ftor) {
for (int i = skip; i < 8; ++i) {
ftor._4 = buf + i*5;
ftor._3 = &i;
(void)ftor._0(ftor);
}
}
int scan_in_double(functor_t in) {
// unpack the args
const char * p = in._4;
int offset = *(int*)in._3;
int * count = in._1;
double * dest = in._2;
// do the work
return *count += sscanf(p, "%lg", dest + offset);
}
int print_repeated(functor_t in) {
// unpack the args
const char * p = in._4;
// do the work
char tmp[10] = {0};
sscanf(p, "%s", tmp);
printf("%s %s\n", tmp, tmp);
return 0;
}
int main()
{
char line[50] = "12.3 34.5 56.7 78.9 32.1 65.4 87.4 65.0";
int nread = 0;
double vals[8] = {0};
functor_t ftor1 = { scan_in_double, &nread, vals };
process_string(line, 3, ftor1);
// check that it worked properly
printf("nread: %d\n", nread);
for (int i = 0; i < 8; ++i) {
printf("vals[%d]: %f\n", i, vals[i]);
}
functor_t ftor2 = { print_repeated };
process_string(line, 0, ftor2);
return 0;
}
EDIT: в ответ на предложение @supercat (https://stackoverflow.com/a/63332205/1206102) я переработал мой пример для передачи двойного косвенного указателя на функцию (который, кстати, сделал самореферентность ненужной) и добавил дополнительный случай: сканирование в ints. Возможность сканировать по разным типам лучше иллюстрирует необходимость наличия аргумента void* как в структуре функтора, так и в указателе функции sig. Вот новый код:
#include <stdio.h>
typedef int (func_t)(int offset, const char * src, void * extra);
typedef struct { func_t * func; void * data; } ftor_t;
typedef struct { int * count; double * dest; } extra_dbl_t;
typedef struct { int * count; int * dest; } extra_int_t;
void process_string(char * buf, int skip, func_t ** func) {
ftor_t * ftor = (ftor_t*)func; // <---- strict-alias violation? or UB?
for (int i = skip; i < 8; ++i) {
(void)ftor->func(i, buf+i*5, ftor->data);
}
}
int scan_in_double(int offset, const char * src, void * extra) {
extra_dbl_t * in = extra;
return *in->count += sscanf(src, "%lg", in->dest + offset);
}
int scan_in_int(int offset, const char * src, void * extra) {
extra_int_t * in = extra;
return *in->count += sscanf(src, "%d", in->dest + offset);
}
int print_repeated(int offset, const char * src, void * extra) {
// extra not used
char tmp[10] = {0};
sscanf(src, "%s", tmp);
printf("%s %s\n", tmp, tmp);
return 0;
}
int main()
{
// contrived strings to make the simplistic +5 in process_string work
// (the real process_string would use whitespace to non-whitespace
// transition)
char dbl_line[50] = "12.3 34.5 56.7 78.9 32.1 65.4 87.4 65.0";
char int_line[50] = "1234 3456 5678 7890 3210 6543 8743 6501";
int n_ints_read = 0;
int int_vals[8] = {0};
extra_int_t int_data = { .count=&n_ints_read, .dest=int_vals };
ftor_t ftor0 = { scan_in_int, &int_data };
process_string(int_line, 0, &ftor0.func);
// check that it worked properly
printf("n_ints_read: %d\n", n_ints_read);
for (int i = 0; i < 8; ++i) {
printf("int_vals[%d]: %d\n", i, int_vals[i]);
}
int n_dbls_read = 0;
double dbl_vals[8] = {0};
extra_dbl_t dbl_data = { .count=&n_dbls_read, .dest=dbl_vals };
ftor_t ftor1 = { scan_in_double, &dbl_data };
process_string(dbl_line, 3, &ftor1.func);
// check that it worked properly
printf("n_dbls_read: %d\n", n_dbls_read);
for (int i = 0; i < 8; ++i) {
printf("dbl_vals[%d]: %f\n", i, dbl_vals[i]);
}
ftor_t ftor2 = { print_repeated }; // no extra data req'd
process_string(dbl_line, 0, &ftor2.func);
return 0;
}
Но если вместо этого я приму ptr для структуры/функтора:
void process_string(char * buf, int skip, ftor_t * ftor) {
for (int i = skip; i < 8; ++i) {
(void)ftor->func(i, buf+i*5, ftor->data);
}
}
И измените сайт вызова на:
process_string(dbl_line, 0, &ftor2); // not &ftor2.func
Тогда в process_string() нет приведения указателя и, следовательно, нет нарушения строгого псевдонима. Я думаю.
В обоих случаях новый вывод:
n_ints_read: 8
int_vals[0]: 1234
int_vals[1]: 3456
int_vals[2]: 5678
int_vals[3]: 7890
int_vals[4]: 3210
int_vals[5]: 6543
int_vals[6]: 8743
int_vals[7]: 6501
n_dbls_read: 5
dbl_vals[0]: 0.000000
dbl_vals[1]: 0.000000
dbl_vals[2]: 0.000000
dbl_vals[3]: 78.900000
dbl_vals[4]: 32.100000
dbl_vals[5]: 65.400000
dbl_vals[6]: 87.400000
dbl_vals[7]: 65.000000
12.3 12.3
34.5 34.5
56.7 56.7
78.9 78.9
32.1 32.1
65.4 65.4
87.4 87.4
65.0 65.0
return 0;
- person textral   schedule 08.08.2020'-Wpedantic' '-Wall' '-Wextra' '-Wconversion'
, не использовался. Удаление отвлекающих предупреждений вносит ясность в пост. - person chux - Reinstate Monica   schedule 08.08.2020n
равно 3 для инициализацииftor1
и 1 для инициализацииftor2
) - person textral   schedule 08.08.2020_
, зарезервированы только для определенных целей. Вы не должны использовать такие идентификаторы. - person RobertS supports Monica Cellio   schedule 08.08.2020