【问题标题】:Instead of using sizeof(type), use sizeof *p,is it safe and correct?不使用sizeof(type),使用sizeof *p,是否安全正确?
【发布时间】:2020-02-18 10:55:23
【问题描述】:

这样使用安全吗,使用gcc 4.9.2编译的代码没有任何错误或警告

widget *p;
...
p = malloc(sizeof *p);

我在 SEI CERT C 编码标准 网站上找到了这个。

点击here -- 没有类型不匹配的问题,不需要强制转换。您每次都分配适量的内存。


struct widget;
typedef struct widget widget_t;

struct gadget;
typedef struct gadget gadget_t;

widget_t *newWidget(void)
{
    widget_t *p = malloc(sizeof *p);
    if (p) 
        /* initialize members of *p as necessary */
    return p;
} 

gadget_t *newGadget(void)
{
    gadget_t *p = malloc(sizeof *p);
    if (p)
        /* initialize members of *p as necessary */
    return p;
}

void deleteWidget(widget_t **p)
{
     /* delete any subelements of *p */
     free(*p);
     *p = NULL;
}

void deleteGadget(gadget_t **p)
{
    /* delete any subelements of *p */
    free(*p);
    *p = NULL;
}

...

widget_t *p = newWidget();
gadget_t *g = newGadget();

if (p)
    /* do stuff with p */

if (g)
    /* do stuff with g */
...
deleteWidget(&p); 
deleteGadget(&g); 

【问题讨论】:

  • p = malloc(sizeof *p)p = malloc(sizeof (some_type)) 更容易查看。第二种形式要求检查“类型是否正确”。第一个在构造上是正确的。

标签: c malloc


【解决方案1】:

sizeof 运算符有如下定义

sizeof unary-expression
sizeof ( type-name )

因此在此声明中

widget_t *p = malloc(sizeof *p);

使用第一种形式的运算符,其中表达式*p 是一元表达式,类型为widget_t

因此这些声明

widget_t *p = malloc(sizeof *p);
widget_t *p = malloc(sizeof( widget_t ) );

完全等价。

第一个声明更可取,因为 sizeof 运算符中的表达式不依赖于实际类型。即指针的类型可以改变,但声明将有效,无需任何其他更改。

在 C 中,不需要将从 malloc 返回的指针转换为分配的左值的类型,因为 void * 类型的指针可以分配给指向任何类型对象的指针。它有时用于(而且有时很有用)使程序自我记录。

【讨论】:

  • widget_t *p = malloc(sizeof( widget_t ) );那我们为什么要使用类型转换呢?
  • @bhura 这在 C 中是多余的,因为 void * 类型的指针可以分配给指向任何对象类型的指针。转换有时用于代码的自我记录。
  • @bhura: 只有在 C++(不允许将 void * 隐式转换为其他指针类型)或在 C89 之前的实现中(其中不存在 void *)中才需要强制转换*alloc 函数返回 char *)。否则,这是不必要的,只会增加维护负担。
  • @JohnBode“只会增加维护负担”是完全错误的说法。在 C 中使用强制转换可以避免许多难以发现的错误,有时还可以使代码自我记录。
  • @JohnBode 考虑两个例子。 p = malloc( sizeof( *p ) );你能说p的类型是什么吗?你能说它是否是正确的内存分配吗?例如,在指针 p 的声明和该语句之间可以有几个屏幕(或页面)。您需要前后滚动源代码以确定分配的有效性和 p 的声明。此外,p 甚至可以在不同的模块中声明。
【解决方案2】:

这是一个很好的编码习惯!

想象一下这段代码

struct structv1 *p1 = malloc(sizeof (struct structv1));
struct structv1 *p2 = malloc(sizeof *p2);

改为

struct structv2 *p1 = malloc(sizeof (struct structv2));
//     ^^^^^^^^                     ^^^^^^^^^^^^^^^^^
// two changes! maybe the programmer forgets one of them
struct structv2 *p2 = malloc(sizeof *p2);
//     ^^^^^^^^
// one change only. the argument to malloc is already correct

【讨论】:

    【解决方案3】:

    简短回答:是的,它是安全的。

    sizeof 不是函数; it's an operator。正如您所展示的那样,它返回表达式的 type 的对象表示的大小(以字节为单位)。表达式本身不在运行时计算;相反,它在编译时将 type 提供给 sizeof 运算符,因此不会造成伤害或犯规。

    【讨论】:

      【解决方案4】:

      使用sizeof *p 代替sizeof (<em>type</em>) 是安全的,但有一个例外 - 如果p 是指向可变长度数组的未初始化或无效指针,则行为未定义。例如:

      size_t rows, cols;
      ...
      T (*p)[cols] = malloc( rows * sizeof *p );  // *p is undefined here
      

      对于每个类型除了可变修改的类型,sizeof 的操作数不会被计算。对于可变长度数组,它被评估,并且由于pmalloc 调用完成之前是无效的,因此对其应用* 运算符会导致未定义的行为。

      Chapter and verse

      6.5.3.2 地址和间接运算符
      ...
      4 一元<strong>*</strong> 运算符表示间接。如果操作数指向一个函数,则结果为 功能指示符;如果它指向一个对象,则结果是一个左值,指定 目的。如果操作数的类型为“指向 type”的指针,则结果的类型为“type”。如果 无效值已分配给指针,一元 <strong>*</strong> 运算符的行为是 未定义。102)
      ...
      6.5.3.4 sizeof 和 _Alignof 运算符
      ...
      2 <strong>sizeof</strong> 运算符产生其操作数的大小(以字节为单位),可能是 表达式或类型的括号名称。大小由类型决定 操作数。结果是一个整数。如果操作数的类型是变长数组 类型,计算操作数;否则,不计算操作数,结果为 整数常量。
      102) ... 一元 <strong>*</strong> 运算符取消引用指针的无效值包括空指针、 地址与指向的对象的类型不恰当地对齐,并且对象的地址在 生命周期结束。

      现在,我已经多次使用上述代码并且从未遇到任何问题,但这并不能保证任何事情 - 我只在一个特定架构 (x68) 上使用过它并且范围有限的编译器。无法保证它不会在某些古怪的架构上壮观地炸毁。

      不过,这是一个非常有用的习语,我有点希望标准措辞更好,以正确定义它。我没有最新版本的副本(2011 年是我可以访问的最新版本),所以我不知道他们是否调整了该语言。

      【讨论】:

        猜你喜欢
        • 2019-10-18
        • 1970-01-01
        • 2013-09-19
        • 2012-10-21
        • 1970-01-01
        • 1970-01-01
        • 2018-04-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多