变量参数的指定初始值设定项
Designated initializers for variable arguments
我正在编写一个基于文本的 Minecraft 机器人,主要是为了(尝试)惹恼一些 players/admins 并查看 Minecraft protocol.
的内部结构
无论如何,我仍处于设计阶段,正在编写发送和接收数据包的函数。声明现在看起来像这样:
int send_packet(int sock, con_state_t state, send_packet_t ptype, ...);
int recv_packet(int sock, con_state_t state, recv_packet_t ptype, ...);
(我计划为错误和套接字类型添加 typedef
s,两者都是 int
s。)
到目前为止,还不错。我知道可变参数是类型不安全的,但与 struct
s 相比,这使我能够编写更好的代码。我可以编写这样的代码(send_packet
函数):
/* BTW: I had to decide between structs passed as void* to imitate
* inheritance and variable-length arguments.
* Both are equally type-unsafe but variable-length arguments offer the
* significant advantage of portably iterating over them. Therefore, I can
* implement a packet-independent packet sending function without remarkable
* struggle or packet-specific functions, which would be QUITE a hassle.
* A drawback is that for every argument from the variable-length arguments,
* data has to be pushed on the stack, so the stack frame grows linearly
* to the argument count. Not so with a struct; only the address of it had to
* be passed.
* (Is variable-length argument the right term at all? I deduced it from
* variable-length array...)
*/
int send_packet(int sock, send_packet_t ptype, ...) {
va_list fields;
uint8_t data[0]; /* TODO: replace 0 by something like PACK_MAX_LEN
* or so! */
/* TODO: as mentioned above, data should actually have the size of
* PACK_MAX_LEN. because this might be very big, you could use some
* dynamic memory allocation mechanism! same goes for string_t, chat_t,
* and all similar data types. */
uint8_t* data_it = data;
/* TODO: ADD PREPENDING OF PACKET ID AND PACKET LENGTH HERE! */
/* TODO: this function isn't finished. add some code, man! resuming the
* above comment. */
/* TODO: add support for legacy server list ping! (packet id = 0xfe) */
/* TODO: for more complex data types: add code to convert from
* simple data types to these complex ones like fix_varint_t to
* varint_t! */
va_start(fields, ptype);
for (size_t i = 0; i < sp_structs[ptype].len; ++i) {
/* Jeez, I hope my code is understandable... */
switch (sp_structs[ptype].fields[i]) {
case map_boolean:
cpy_data_sp(boolean_t);
break;
case map_byte:
cpy_data_sp(byte_t);
break;
case map_ubyte:
cpy_data_sp(ubyte_t);
break;
case map_short:
cpy_data_sp(short_t);
break;
case map_ushort:
cpy_data_sp(ushort_t);
break;
case map_int:
cpy_data_sp(int_t);
break;
case map_long:
cpy_data_sp(long_t);
break;
case map_chat:
cpy_data_sp(chat_t);
break;
case map_string:
cpy_data_sp(string_t);
break;
case map_varint:
/* TODO: maybe write a macro to generalize wr_varint
* and wr_varlong. */
fix_varint_t fix = va_arg(fields, fix_varint_t);
data_it += wr_varint(data_it, fix);
break;
case map_varlong:
fix_varlong_t fix = va_arg(fields, fix_varlong_t);
data_it += wr_varlong(data_it, fix);
break;
/*case map_chunk:
cpy_data_sp(chunk_t);
break;*/
}
}
/*
if (sock_send(sock, data, packet_len) == -1) {
return -1;
}
*/
va_end(packet);
return 0;
}
我只是想向您展示这一点,这样您就会明白为什么可变参数在这里更好。
使用 struct
s,函数原型如下所示:
int send_packet(int sock, con_state_t state, send_packet_t ptype, void* pack);
pack
指向这里的struct
。现在我们可以用 ptype
推断出数据包类型,但是我们 不能方便地迭代字段 正如上面代码的第一条评论中提到的那样。
使用该函数的每个人都必须知道参数的确切顺序,否则可能会出现未定义的行为。 struct
在这里很好,因为 指定的初始化程序 ,它启用了与顺序无关的成员初始化。
你能想出一些方法来模拟可变参数吗?
也许一些 va_list
魔法?不过,它应该严格符合标准。
注意: 我已经想到使用 C++,这将解决我所有的问题,但我想在带有一些 C11 的漂亮的标准 C99 中执行此操作。
您可以使用 key/value-pairs 而不是定位值:
int func(int argc, ...) {
}
并这样称呼它
func(3,
P_NAME, "John",
P_SURNAME, "Myers",
P_AGE, 10
);
或使用 0 值作为终止符以避免 argc。
我正在编写一个基于文本的 Minecraft 机器人,主要是为了(尝试)惹恼一些 players/admins 并查看 Minecraft protocol.
的内部结构无论如何,我仍处于设计阶段,正在编写发送和接收数据包的函数。声明现在看起来像这样:
int send_packet(int sock, con_state_t state, send_packet_t ptype, ...);
int recv_packet(int sock, con_state_t state, recv_packet_t ptype, ...);
(我计划为错误和套接字类型添加 typedef
s,两者都是 int
s。)
到目前为止,还不错。我知道可变参数是类型不安全的,但与 struct
s 相比,这使我能够编写更好的代码。我可以编写这样的代码(send_packet
函数):
/* BTW: I had to decide between structs passed as void* to imitate
* inheritance and variable-length arguments.
* Both are equally type-unsafe but variable-length arguments offer the
* significant advantage of portably iterating over them. Therefore, I can
* implement a packet-independent packet sending function without remarkable
* struggle or packet-specific functions, which would be QUITE a hassle.
* A drawback is that for every argument from the variable-length arguments,
* data has to be pushed on the stack, so the stack frame grows linearly
* to the argument count. Not so with a struct; only the address of it had to
* be passed.
* (Is variable-length argument the right term at all? I deduced it from
* variable-length array...)
*/
int send_packet(int sock, send_packet_t ptype, ...) {
va_list fields;
uint8_t data[0]; /* TODO: replace 0 by something like PACK_MAX_LEN
* or so! */
/* TODO: as mentioned above, data should actually have the size of
* PACK_MAX_LEN. because this might be very big, you could use some
* dynamic memory allocation mechanism! same goes for string_t, chat_t,
* and all similar data types. */
uint8_t* data_it = data;
/* TODO: ADD PREPENDING OF PACKET ID AND PACKET LENGTH HERE! */
/* TODO: this function isn't finished. add some code, man! resuming the
* above comment. */
/* TODO: add support for legacy server list ping! (packet id = 0xfe) */
/* TODO: for more complex data types: add code to convert from
* simple data types to these complex ones like fix_varint_t to
* varint_t! */
va_start(fields, ptype);
for (size_t i = 0; i < sp_structs[ptype].len; ++i) {
/* Jeez, I hope my code is understandable... */
switch (sp_structs[ptype].fields[i]) {
case map_boolean:
cpy_data_sp(boolean_t);
break;
case map_byte:
cpy_data_sp(byte_t);
break;
case map_ubyte:
cpy_data_sp(ubyte_t);
break;
case map_short:
cpy_data_sp(short_t);
break;
case map_ushort:
cpy_data_sp(ushort_t);
break;
case map_int:
cpy_data_sp(int_t);
break;
case map_long:
cpy_data_sp(long_t);
break;
case map_chat:
cpy_data_sp(chat_t);
break;
case map_string:
cpy_data_sp(string_t);
break;
case map_varint:
/* TODO: maybe write a macro to generalize wr_varint
* and wr_varlong. */
fix_varint_t fix = va_arg(fields, fix_varint_t);
data_it += wr_varint(data_it, fix);
break;
case map_varlong:
fix_varlong_t fix = va_arg(fields, fix_varlong_t);
data_it += wr_varlong(data_it, fix);
break;
/*case map_chunk:
cpy_data_sp(chunk_t);
break;*/
}
}
/*
if (sock_send(sock, data, packet_len) == -1) {
return -1;
}
*/
va_end(packet);
return 0;
}
我只是想向您展示这一点,这样您就会明白为什么可变参数在这里更好。
使用 struct
s,函数原型如下所示:
int send_packet(int sock, con_state_t state, send_packet_t ptype, void* pack);
pack
指向这里的struct
。现在我们可以用 ptype
推断出数据包类型,但是我们 不能方便地迭代字段 正如上面代码的第一条评论中提到的那样。
使用该函数的每个人都必须知道参数的确切顺序,否则可能会出现未定义的行为。 struct
在这里很好,因为 指定的初始化程序 ,它启用了与顺序无关的成员初始化。
你能想出一些方法来模拟可变参数吗?
也许一些 va_list
魔法?不过,它应该严格符合标准。
注意: 我已经想到使用 C++,这将解决我所有的问题,但我想在带有一些 C11 的漂亮的标准 C99 中执行此操作。
您可以使用 key/value-pairs 而不是定位值:
int func(int argc, ...) {
}
并这样称呼它
func(3,
P_NAME, "John",
P_SURNAME, "Myers",
P_AGE, 10
);
或使用 0 值作为终止符以避免 argc。