将char *重新解释为另一种原始类型的数组以进行只读操作的任何合法方法?

时间:2016-12-26 20:35:54

标签: c performance language-lawyer

使用函数是否有任何合法的方法来解释给定长度的char *参数作为不同整数类型的指针,然后访问所述转换后的指针?似乎有很多非法(UB)方法可以做到这一点......

例如给出以下函数原型:

int32_t sum_32(char *a, int len);

我想知道是否有一种方法可以合法地编写与以下代码等效的内容:

int32_t sum_32(char *a, int len) {
    assert(len % 4 == 0);
    int32_t total = 0;
    for (int i = 0; i < len / 4; i++) {
        total += ((int32_t *)a)[i]; 
    }
    return total;
}

当然,这样做的一种方法就是将访问分解为字符大小的访问,转而重新组合成一个更大的值(有一些关于字节序的假设,这里假设为LE):

int32_t sum_32(char *a, int len) {
    assert(len % 4 == 0);
    int32_t total = 0;
    for (int i = 0; i < len; i += 4) {
        int32_t val = (int32_t)
                      (a[i+0] <<  0) +
                      (a[i+1] <<  8) +
                      (a[i+2] << 16) +
                      (a[i+3] << 24) ;
        total += val; 
    }
    return total;
}

...但是我在这里寻找一次访问基础数组int32_t的解决方案。

如果答案是&#34;它不可能&#34;,如果我知道char *a的来源是分配函数,答案是否会改变 - 或者更广泛地说,是我可以对a施加任何其他限制,以便将其作为更大的类型进行访问是合法的吗?

2 个答案:

答案 0 :(得分:2)

如果内存最后写为int32_t或任何兼容类型,则有效类型变为int32_t,您可以使用简单的强制转换来阅读它。否则,如果不打破别名规则就不可能。

答案 1 :(得分:2)

为避免严格的别名问题,total += ((int32_t *)a)[i];可以替换为:

int32_t temp;
memcpy(&temp, a+i*4, sizeof temp);
total += temp;

编译器将优化其中不实际调用memcpy库函数。当然,只有在想要预期的字节序含义时才使用它;否则使用位移版本。

(注意:正如问题中所写,由于char被签名的可能性,位移版本是错误的 - 您需要将函数更改为unsigned char *或使用等效函数投射)。

我使用compiler explorer并发现对于此代码,gcc将测试a是否对齐,如果是,则使用XMM指令,否则使用旧指令。