通过void指针

时间:2018-03-24 10:03:53

标签: c pointers memory-management constraints undefined-behavior

我知道使用联合消除了在访问具有不同类型的同一内存块时一定存在未定义行为问题的担忧。

我想知道用malloc()和void指针分配的内存是否也是如此;以下代码是否表现出任何形式的未定义行为或违反任何约束?

#include <stdio.h>
#include <stdlib.h>

typedef union memblk {
    int x;
    double y;
    long long z;
} memblk;

int main(void)
{
    /*
    TYPE *p_spc = (TYPE *)malloc(szAlloc);

    *(OTHER_TYPE *)p_spc = some_value;  // CONSTRAINT VIOLATION 
    ...
    */

    memblk st_var;

    // Assigning (int) value
    st_var.x = 10;
    printf("value of x(int): %d\n", st_var.x);

    // Assigning (double) value
    st_var.y = 3.14;
    printf("value of y(double): %.2f\n", st_var.y);

    // Assigning (long long) value
    st_var.z = 1000;
    printf("value of z(long long): %lld\n\n", st_var.z);

    /*..............................................*/
    size_t szAlloc = sizeof(long long) > sizeof(double) ?
                     sizeof(long long) : sizeof(double);

    void *p_spc = malloc(szAlloc);

    // Assigning (int) value
    *(int *)p_spc = 10;
    printf("value of (int): %d\n", *(int *)p_spc);

    // Assigning (double) value
    *(double *)p_spc = 3.14;
    printf("value of (double): %.2f\n", *(double *)p_spc);

    // Assigning (long long) value
    *(long long *)p_spc = 1000;
    printf("value of (long long): %lld\n", *(long long *)p_spc);

    free(p_spc);
    //system("pause");
    return 0;
}

2 个答案:

答案 0 :(得分:0)

根据定义,long long的大小至少与int的大小一样大,因此malloc()分配的大小足以容纳所有3种类型。

C标准保证malloc()返回的指针与任何标准类型的访问正确对齐。因此,上面的代码是为存储给定类型并检索它的每个部分完美定义的。

我想不出为什么你不能在程序的不同部分使用相同的内存用于不同用途的原因。既然你不通过指向不同类型的指针交织访问,也不试图将一种类型的表示重新解释为另一种类型,我认为整个序列的行为是完全定义的。

但请注意,您应该测试malloc()的返回值。在malloc()失败的系统上,程序确实会有未定义的行为。

答案 1 :(得分:0)

有些编译器,比如gcc和clang,当不使用-fno-strict-aliasing强制它们遵守C标准时,无法可靠地处理在不同时间使用存储作为不同类型的代码,即使是非重叠方式也是如此实际上没有涉及别名的方式。

如果你正在使用一个正确处理不涉及别名的情况的编译器,那么你应该没问题,只要在派生左值D或从它派生的任何东西中完成的任何事情都在任何之前完成。发生以下情况:

  1. 使用不是从D派生的左值以相互冲突的方式访问存储(读取与写入冲突而不是读取;写入与读取和写入冲突)。

  2. 推导出左值,而不是来自D,它将来会在某个时候直接或间接地以冲突方式访问存储。

  3. 执行到达函数或循环的开始,其中出现上述情况之一。

  4. 不幸的是,gcc和clang都忽略了指针和左值派生的时间,这意味着它们通常会采用不会出现别名的代码(如果按照写入的方式执行)并以引入别名的方式重写它,而盲目地假设没有将出现锯齿。例如,给定序列:

    1. 获取someUnionArray [i] .member1。
    2. 的地址
    3. 使用该指针,然后放弃它。
    4. 获取someUnionArray [j] .member2。
    5. 的地址
    6. 使用该指针,然后放弃它。
    7. 获取someUnionArray [i] .member1。
    8. 的地址
    9. 使用该指针,然后放弃它。
    10. gcc和clang都将保留指针从步骤1开始并在步骤6中重复使用它,盲目地假设没有其他任何东西扰乱了所讨论的对象。重用地址计算的结果就可以了,但是对于涉及同一个union对象的任何前面的操作,需要按顺序保存每个接受union成员地址的操作。

      可以使用联合处理正确语义的编译器不应该对指针类型转换应用相同的逻辑有任何困难,但由于gcc和clang甚至无法处理简单明显的情况,我认为使用它是不安全的默认方言,在任何情况下,存储将被用作多种类型,即使标准似乎是允许的方式。

      (*)请注意,从技术上讲,使用该成员类型的左值访问结构或联合成员总是违反N1570 p6.5p7,因此除非该成员属于字符类型,否则将调用UB,但这样的读数将是荒谬的。 struct和union类型有意义的唯一方法是,如果在不使用lvalue的情况下使用成员类型来访问从struct或union成员派生的lvalue,那么就像所写的那样,将使用别名其他