动态数组未知大小 C

Dynamic array unknown size C

我使用 pjsip 已经有一段时间了,为了让它适用于 UWP,我在基本 C 代码中遇到了一个看似致命的缺陷。我在下面的代码中收到错误 'addrs': unknown size'deprecatedAddrs': unknown size

PJ_DEF(pj_status_t) pj_enum_ip_interface2( const pj_enum_ip_option *opt,
                       unsigned *p_cnt,
                       pj_sockaddr ifs[]) {
    pj_enum_ip_option opt_;

    if (opt)
    opt_ = *opt;
    else
    pj_enum_ip_option_default(&opt_);

    if (opt_.af != pj_AF_INET() && opt_.omit_deprecated_ipv6) {
    pj_sockaddr addrs[*p_cnt];
    pj_sockaddr deprecatedAddrs[*p_cnt];
    unsigned deprecatedCount = *p_cnt;
    unsigned cnt = 0;
    int i;
    pj_status_t status;
...

错误来自 pj_sockaddr addrs[*p_cnt];pj_sockaddr deprecatedAddrs[*p_cnt]; 两行。我觉得我明白这应该行不通,因为 C 不能有动态数组并且需要预分配 space。但是,pjsip 是一个经常使用的成熟库,所以我的问题是:是否存在它可以正常工作的情况?

这是有效的 c99 语法。它被称为 variable-length array。您可能希望将 -std=c99 标志添加到您的编译器。

编辑: 鉴于下面的评论,这里有一个关于如何使用动态内存替换 VLA 的示例。

PJ_DEF(pj_status_t) pj_enum_ip_interface2( const pj_enum_ip_option *opt,
                       unsigned *p_cnt,
                       pj_sockaddr ifs[]) {
    pj_enum_ip_option opt_;

    if (opt)
    opt_ = *opt;
    else
    pj_enum_ip_option_default(&opt_);

    if (opt_.af != pj_AF_INET() && opt_.omit_deprecated_ipv6) {
    
    /* Dynamically allocate memory to replace VLAs
     * Use calloc if you need it initialized to zero
     */ 
    pj_sockaddr *addrs = malloc((*p_cnt) * sizeof (pj_sockaddr));
    pj_sockaddr *deprecatedAddrs = malloc((*p_cnt) * sizeof (pj_sockaddr));
    
    unsigned deprecatedCount = *p_cnt;
    unsigned cnt = 0;
    int i;
    pj_status_t status;

    ...


    /* Remember that memory allocated from the heap needs to be freed */
    free (addrs);
    free (deprecatedAddrs);
    
    /* Not needed, but a good practice */
    addrs = NULL; 
    deprecatedAddrs = NULL;

    ...
    
    return status;

MSVC 编译器不支持 C99 标准的所有功能,包括可变长度数组。您可以通过更改以下行使此代码与 MSVC 兼容:

pj_sockaddr addrs[*p_cnt];
pj_sockaddr deprecatedAddrs[*p_cnt];

使用malloc:

pj_sockaddr *addrs = malloc(*p_cnt * sizeof(pj_sockaddr));
pj_sockaddr *deprecatedAddrs = malloc(*p_cnt * sizeof(pj_sockaddr));

如果您使用的是 gcc 或 clang,则标志 std=c99 或类似的东西。

VLA:s 来自 C99,但在 C11 中被设为可选。

如果您使用的是 MSVC,那您就不走运了。它不支持 C99。这里有一个问题:Does Visual Studio 2017 fully support C99?

如果您想将代码更改为等效的代码,则可以使用 alloca。所以改变

pj_sockaddr addrs[*p_cnt]; 

pj_sockaddr *addrs = alloca(sizeof *addrs * *p_cnt);

无论如何,这基本上就是 VLA:s 幕后发生的事情,所以这是一个快速修复。唯一的区别是,如果您稍后在代码中使用 sizeof 运算符,那么如果某处有一个看起来像这样的循环:

for(int i=0; i<sizeof addrs; i++) {
    // Code
}

那么你也必须为此做点什么。

您可以使用 malloc 而不是 alloca,但这可能会影响性能,之后您需要 free 内存。通常,我更喜欢使用 malloc 但由于您不是从头开始编写它所以它并不重要。毕竟,由于原始代码使用的是 VLA:s,因此您不会添加任何问题。

简而言之,VLA:s(和 alloca)的问题是您不能对分配进行错误检查,否则您就有炸毁堆栈的风险。我在这里写了一个答案: