【问题标题】:Designated initializers for variable arguments变量参数的指定初始化器
【发布时间】:2015-12-30 14:35:08
【问题描述】:

我正在编写一个基于文本的 Minecraft 机器人,主要是为了(试图)惹恼一些玩家/管理员并检查 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, ...);

(我计划为错误和套接字类型添加typedefs,两者都是ints。)

到目前为止,一切都很好。我知道变量参数是类型不安全的,但使我能够编写比structs 更好的代码。我可以编写这样的代码(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;
}

我只是想向你展示这一点,这样你就会明白为什么可变参数在这里更好。
使用structs,函数原型如下所示:

 int send_packet(int sock, con_state_t state, send_packet_t ptype, void* pack);

pack 指向此处的struct。现在我们可以使用ptype 推断数据包类型,但我们无法方便地遍历字段,如上述代码的第一条注释中所述。

使用该函数的每个人都必须知道参数的确切顺序,否则可能会发生未定义的行为。 structs 在这里很好,因为 指定的初始化程序,它启用了与顺序无关的成员初始化。
你能想出一些方法来模拟变量参数吗?
也许有一些va_list 魔法?不过,它应该是严格符合的。

注意:我已经想到了使用 C++,这将解决我的所有问题,但我想在带有一些 C11 的漂亮、标准 C99 中做到这一点。

【问题讨论】:

    标签: c struct minecraft variadic-functions


    【解决方案1】:

    您可以使用键/值对而不是定位值:

     int func(int argc, ...) {
    
     }
    

    然后这样称呼它

    func(3, 
             P_NAME,    "John",
             P_SURNAME, "Myers", 
             P_AGE,     10
        );
    

    或使用 0 值作为终止符来避免 argc。

    【讨论】:

    • 好主意!唯一的缺点是传递的参数数量增加了一倍,因此必须传递更多的数据。你有没有积极使用它的代码?
    • 不,我还没用过这个。参数所需的堆栈大小约为双倍大小,这是正确的。但是,如果您不使用此类函数(或资源非常有限的平台的代码)进行递归,这在实践中可能并不明显。
    猜你喜欢
    • 2010-12-07
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2017-02-13
    • 1970-01-01
    • 2014-07-27
    • 1970-01-01
    相关资源
    最近更新 更多