Suppose I want to implement a stack in C, with some various peculiarities, like deciding how to reallocate when it expands in a certain way, and so on. So something like
假设我想用C语言实现一个堆栈,它具有一些不同的特性,比如决定当它以某种方式展开时如何重新分配,等等。所以像这样的东西
typedef struct stack_int {
unsigned length;
unsigned allocated_length;
int *data_pointer;
} stack_int;
extern int stack_int_pop(stack *l);
and so on. But in my program, I would like to reuse it where int
above gets replaced by, say, 5 data structures of my own (some structs which I use). In all cases I want the details to be the same.
诸若此类。但在我的程序中,我想在上面的int被替换为我自己的5个数据结构(我使用的一些结构)的地方重复使用它。在所有情况下,我都希望细节是相同的。
Question: What is the "correct", or "idiomatic C", way to do that?
问:做这件事的“正确”或“惯用的C”方式是什么?
I can think of various roundabouts, like making a generic stack, with a field element_size
, but then the interface when popping and appending would be necessarily ugly and also not optimized, as far as I can see. I also found a roundabout which is replacing the above by
我可以想到各种环行交叉口,比如创建一个带有Element_Size字段的泛型堆栈,但在我看来,弹出和追加时的接口必然会很难看,而且也没有优化。我还找到了一个环形交叉路口,上面的位置改为
#define CONCAT_PRE(X, Y) X ## _ ## Y
#define CONCAT(X, Y) CONCAT_PRE(X, Y)
#define STACK_TYPE CONCAT(stack, STACK_ELEMENT_TYPE)
typedef struct STACK_TYPE {
unsigned length;
unsigned allocated_length;
STACK_ELEMENT_TYPE *data_pointer;
} STACK_TYPE;
STACK_ELEMENT_TYPE CONCAT(STACK_TYPE, pop)(stack *l) {
...
}
and then including it several times:
然后几次将其包括在内:
#define STACK_ELEMENT_TYPE int
#include "stack.c"
#define STACK_ELEMENT_TYPE my_struct
#include "stack.c"
but this does not seem like a "idiomatic C" way, is very confusing to someone who will read it, prevents a single header file, and so on and so on...
但这看起来并不像是一种“惯用的C”方式,对于会阅读它的人来说是非常令人困惑的,防止单个头文件,等等……
更多回答
How about using a generic void *
pointer, together with an element_size
? And instead of the pop function returning the value, pass a pointer to the value to the function as an argument, and use memcpy
to copy the value to it.
使用通用的空*指针和ELEMENT_SIZE如何?并且不是POP函数返回值,而是将指向该值的指针作为参数传递给该函数,并使用Memcpy将该值复制到该函数。
@Someprogrammerdude That is what I thought, but first this creates an ugly interface, in the sense that the user needs to always remember how to do it (the pop and append) in a strange way, and second, the call to memcpy seems quite wasteful to me, for just a few bytes at a time, isn't it? Seems a "hack"...
@Someprogrammerdude这就是我所想的,但首先这会创建一个丑陋的接口,在这个意义上,用户需要总是记住如何以一种奇怪的方式完成它(pop和append),其次,对memcpy的调用对我来说似乎很浪费,一次只有几个字节,不是吗?好像是“黑客”...
Then how about a simple generic "stack" structure, without the pointer to the data, but with pointers to functions. Then a set of structures that "inherit" from the base stack structure (it includes the base stack structure as the first member) for each specific type? Then the base interface calls the correct functions using the function pointers. Still need to pass pointers to values instead of returning values.
那么简单的通用“堆栈”结构怎么样?它没有指向数据的指针,但带有指向函数的指针。那么,对于每个特定类型,从基本堆栈结构(它包括作为第一个成员的基本堆栈结构)“继承”的一组结构呢?然后,基接口使用函数指针调用正确的函数。仍然需要传递指向值的指针,而不是返回值。
Thanks, I also thought about this. Maybe it is not too bad. So you mean re-implementing pop and append (in a deterministic copy-paste way) for each new field type?
谢谢,我也想到了。也许还不算太糟。所以你的意思是为每个新的字段类型重新实现pop和append(以确定性的复制粘贴方式)?
Or possibly, using multiple structure with "inheritance" and generic selection?
或者,可能使用带有“继承”和类属选择的多重结构?
I have to admit, I’m not entirely sure what the "most idiomatic" thing would be to do here, but it’s for sure not including the same header multiple times, please refrain from doing that, as it could create dependency loops, and if you’re using a better IDE than Editor.exe, you should already have an include guard in your .h file anyway.
我必须承认,我不完全确定在这里应该做什么,但它肯定不会多次包含相同的头文件,请不要这样做,因为它可能会创建依赖循环,而且如果您使用的是比Editor.exe更好的IDE,那么无论如何您的.h文件中都应该已经有了一个包含保护。
For your problem however, I’d argue to use a
(void *) and a type field consisting of an unsigned char, at least that is how Java classifies it’s data structures. Additionally, you just define numbers that correlate to your own structures.
然而,对于您的问题,我主张使用(void*)和由无符号字符组成的类型字段,至少Java是这样对其数据结构进行分类的。此外,您只需定义与您自己的结构相关的数字。
This method is especially helpful of course, when you already have pointers to your structs you mentioned.
当然,当您已经有指向您提到的结构的指针时,此方法特别有用。
If you’re not keen on using that kind of (admittedly a bit awkward) method, you are always welcome to create multiple stacks that exist concurrently.
如果您不热衷于使用这种方法(诚然有点尴尬),我们总是欢迎您创建同时存在的多个堆栈。
Something like this, using void*
for passing pointers around, and unsigned char *
for doing address calculations.
如下所示,使用void*传递指针,使用unsign char*进行地址计算。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct VoidStack {
unsigned used_length;
unsigned allocated_length;
unsigned elem_bytes;
void *data;
};
void *stack_pop(struct VoidStack *s) {
return (unsigned char *)s->data + (--s->used_length * s->elem_bytes);
}
void stack_push(struct VoidStack *s, void *src) {
if (s->used_length == s->allocated_length) {
// grow data and update allocated_length
}
memcpy((unsigned char *)s->data + (s->used_length++ * s->elem_bytes),
src, s->elem_bytes);
}
// etc
int main(void) {
struct VoidStack intStack;
intStack.used_length = 0;
intStack.allocated_length = 4;
intStack.elem_bytes = sizeof (int);
intStack.data = malloc(4 * intStack.elem_bytes);
int foo = 42, bar = -1, baz = 0, quux = 2023;
stack_push(&intStack, &foo);
stack_push(&intStack, &bar);
stack_push(&intStack, &baz);
stack_push(&intStack, &quux);
printf("unstack: %d", *(int*)stack_pop(&intStack));
printf(" %d", *(int*)stack_pop(&intStack));
printf(" %d", *(int*)stack_pop(&intStack));
printf(" %d\n", *(int*)stack_pop(&intStack));
free(intStack.data);
return 0;
}
See code running on https://ideone.com/4HRYn5 (using xx
rather than intStack
, sorry)
查看在https://ideone.com/4HRYn5上运行的代码(使用xx而不是intStack,抱歉)
Here is a classic way to implement a generic stack mechanism and instantiate the structures and methods using macros. It is easy to extend to other scalar or non-scalar types and allows for implementation variations for cases where the macro generated code is inadequate.
下面是实现通用堆栈机制并使用宏实例化结构和方法的经典方法。它很容易扩展到其他标量或非标量类型,并允许在宏生成的代码不充分的情况下进行实现变化。
Declarations in stack.h:
Stack.h中的声明:
#define DEFINE_STACK_TYPE(T, name) \
typedef struct name { \
int pos; \
int size; \
T *data; \
} name
DEFINE_STACK_TYPE(int, stack_int);
DEFINE_STACK_TYPE(double, stack_double);
DEFINE_STACK_TYPE(char *, stack_string);
/* pop an element from the stack, return 0 if stack is empty */
extern int stack_int_pop(stack_int *s);
extern double stack_double_pop(stack_double *s);
extern char *stack_string_pop(stack_string *s);
/* push an element on the stack, return the index or -1 if allocation failed */
extern int stack_int_push(stack_int *s, int val);
extern int stack_double_push(stack_double *s, double val);
extern int stack_string_push(stack_string *s, char *val);
#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112L
/* generic interface using _Generic() */
#define stack_push(s, val) _Generic(s, \
stack_int *: stack_int_push(s, val), \
stack_double *: stack_double_push(s, val), \
stack_string *: stack_string_push(s, val), \
)
#define stack_pop(s) _Generic(s, \
stack_int *: stack_int_pop(s), \
stack_double *: stack_double_pop(s), \
stack_string *: stack_string_pop(s), \
)
#endif
Definitions in stack.c:
Stack.c中的定义:
#include <stdlib.h>
#include "stack.h"
#define DEFINE_STACK_POP(T, stype, fname) \
T fname(stype *s) { \
return s->pos > 0 ? s->data[--s->pos] : 0; \
}
#define DEFINE_STACK_PUSH(T, stype, fname) \
int fname(stype *s, T val) { \
T *data = s->data; \
while (s->pos >= s->size) { \
int size = s->size + (s->size >> 1) + 32; \
data = realloc(data, sizeof(T) * size); \
if (data == NULL) \
return -1; \
s->data = data; \
s->size = size; \
} \
s->data[s->pos] = val; \
return s->pos++; \
}
DEFINE_STACK_POP(int, stack_int, stack_int_pop)
DEFINE_STACK_POP(double, stack_double, stack_double_pop)
DEFINE_STACK_POP(char *, stack_string, stack_string_pop)
DEFINE_STACK_PUSH(int, stack_int, stack_int_pop)
DEFINE_STACK_PUSH(double, stack_double, stack_double_pop)
DEFINE_STACK_PUSH(char *, stack_string, stack_string_pop)
It is possible to use macros to simulate parameterized types and replace the DEFINE_STACK_TYPE
declarations with:
可以使用宏来模拟参数化类型,并将DEFINE_STACK_TYPE声明替换为:
#define stack(T) \
struct { \
int pos; \
int size; \
T *data; \
}
typedef stack(int) stack_int;
typedef stack(double) stack_double;
typedef stack(char *) stack_string;
Note however that stack(int)
should only be used to define types with typedef
as it would define a different type if used in multiple contexts, such as int stack_int_pop(stack(int) *s);
但需要注意的是,STACK(INT)只能用于定义带有tyfinf的类型,因为如果在多个上下文中使用它会定义一个不同的类型,例如int STACK_INT_POP(STACK(INT)*S);
更多回答
Thanks, but isn't the use of memcpy here a bit disappointing (I am not sure, I am a novice)? it is a function call where you could use equality, it is a (very soft, but still) dependency...
谢谢,但在这里使用Memcpy是不是有点令人失望(我不确定,我是新手)?它是一个函数调用,您可以在其中使用相等,它是一个(非常软,但仍然)依赖...
To use assignment you would have to do a loop: for (size_t k = 0; k < elem_bytes; k++) (unsigned char *)dst + k = (unsigned char *)src + k;
. I find memcpy()
easier on the eyes (and it may even be faster because it can use implementation tricks). Depending on inclusion of the <string.h>
header (otherwise memcpy()
is already part of the Standard Library) is a very small price to pay.
要使用赋值,您必须执行一个循环:for(size_t k=0;k头文件(否则,memcpy()已经是标准库的一部分)是一个非常小的代价。
我是一名优秀的程序员,十分优秀!