【问题标题】:Needless pointer-casts in CC中不必要的指针转换
【发布时间】:2010-09-11 15:32:05
【问题描述】:

我在这个帖子上的回答得到了评论:

Malloc inside a function call appears to be getting freed on return?

简而言之,我有这样的代码:

int * somefunc (void)
{
  int * temp = (int*) malloc (sizeof (int));
  temp[0] = 0;
  return temp;
}

我收到了这条评论:

我只能说,请不要施放 malloc的返回值?它不是 必需,可以隐藏错误。

我同意在 C 中不需要强制转换。在 C++ 中它是强制性的,所以我通常添加它们以防万一有一天我必须在 C++ 中移植代码。

但是,我想知道这样的强制转换如何隐藏错误。有什么想法吗?

编辑:

似乎双方都有非常好的和有效的论点。谢谢大家发帖。

【问题讨论】:

    标签: c++ c pointers


    【解决方案1】:

    将返回 (void *) 的函数转换为 (int *) 是无害的:您将一种类型的指针转​​换为另一种类型。

    将返回整数的函数转换为指针很可能是不正确的。如果你没有明确地转换它,编译器会标记它。

    【讨论】:

      【解决方案2】:

      我发表答案似乎很合适,因为我留下了评论:P

      基本上,如果您忘记包含stdlib.h,编译器将假定malloc 返回int。如果不进行强制转换,您将收到警告。使用投射你不会。

      因此,通过强制转换,您将一无所获,并且冒着抑制合法警告的风险。

      这方面写了很多,快速谷歌搜索会找到更详细的解释。

      编辑

      有人认为

      TYPE * p;
      p = (TYPE *)malloc(n*sizeof(TYPE));
      

      当你不小心没有分配足够的内存时很明显,因为你认为pTYPe 而不是TYPE,因此我们应该强制转换 malloc,因为这种方法的优势覆盖了较小的意外成本禁止编译器警告。

      我想指出两点:

      1. 您应该写 p = malloc(sizeof(*p)*n); 以始终确保您分配正确的空间量
      2. 使用上述方法,如果您更改了 p 的类型,则需要在 3 个地方进行更改:一次在声明中,一次在 malloc 中,一次在演员表中。

      简而言之,我个人仍然认为没有必要强制转换 malloc 的返回值,这当然不是最佳实践。

      【讨论】:

      • :-) 哈哈..你找到了问题。太好了。
      • 这是我能想象到的唯一问题。 ;)
      • 只需使用 -pedantic-errors 运行编译器,以确保始终安全。 ;)
      • @onebyone 我没有引用任何内容。这将要求我将其用作我正在使用的语句的备份。我将它联系起来以显示与同一机构的冲突。我的回答绝不引用任何 CERT 咨询
      • 只要我们在谈论 sizeof,我会说你应该写成: p = malloc(n * sizeof *p);这更短,并且没有在论点周围使用不必要的(在我看来令人困惑和混淆)括号。只有类型名称需要这些,其他对象不需要。
      【解决方案3】:

      一个可能的错误(取决于您是否真正想要)是使用一个大小比例进行mallocing,并分配给不同类型的指针。例如,

      int *temp = (int *)malloc(sizeof(double));
      

      在某些情况下您可能想这样做,但我怀疑它们很少见。

      【讨论】:

        【解决方案4】:

        嗯,我认为恰恰相反——总是直接将其转换为所需的类型。 Read on here!

        【讨论】:

        • 来自同一个站点:securecoding.cert.org/confluence/pages/… :-) 双方的争论当然很有趣
        • 那篇文章中的论点,经过一番思考,并不是一个很好的论点。您应该简单地使用 p = malloc(sizeof(*p)*​​n);一起避免这个问题。现在您不必在 3 个地方更改类型:一次在 p 的声明中,一次在 malloc 中,一次在演员表中。
        • 猜猜如果你问两个程序员,总有第三个更实用的意见。 ;)
        【解决方案5】:

        另一方面,如果您需要将代码移植到 C++,最好使用“new”运算符。

        【讨论】:

        • 什么?但他是用 C 写的,他不能使用“new”。
        • 这就是重点——它不应该是为 C 和 C++ 编写的,它应该是 C 中最好的,然后完全改变为 C++。
        • 不同意 没有必要重写完美的 C 代码只是为了将它与 c++ 模块链接。这只会增加引入新错误的风险,并没有带来任何附加价值。
        • @Nils - 是的。如果您的应用程序覆盖newdelete 以使用自定义内存管理器怎么办?调用mallocfree 的链接C 代码仍将使用内置堆分配器,这可能会更慢和/或不执行您需要内存管理器执行的任何任务。您将无法跟踪 C 组件的内存管理。如果可能,您应该在移植到 C++ 时为 C++ 重写。有时这是不必要的,但有时却很有必要。
        【解决方案6】:

        实际上,转换可以隐藏错误的唯一方法是,如果您正在从一种数据类型转换为较小的数据类型并丢失数据,或者您正在将梨转换为苹果。举个例子:

        int int_array[10];
        /* initialize array */
        int *p = &(int_array[3]);
        short *sp = (short *)p;
        short my_val = *sp;
        

        在这种情况下,转换为 short 会从 int 中删除一些数据。然后是这个案例:

        struct {
            /* something */
        } my_struct[100];
        
        int my_int_array[100];
        /* initialize array */
        struct my_struct *p = &(my_int_array[99]);
        

        您最终会指向错误类型的数据,甚至指向无效的内存。

        但总的来说,如果您知道自己在做什么,那么进行选角就可以了。更重要的是,当您从 malloc 获取内存时,它恰好返回一个 void 指针,除非您强制转换它,否则您根本无法使用它,并且大多数编译器会警告您如果您正在转换为左值(作业的左侧)无论如何都不能接受。

        【讨论】:

          【解决方案7】:

          我认为你应该放入演员表。考虑类型的三个位置:

          T1 *p;
          p = (T2*) malloc(sizeof(T3));
          

          这两行代码可能相距甚远。因此编译器将enforce T1 == T2 很好。更容易直观地验证 T2 == T3。

          如果你错过了 T2 演员,那么你必须希望 T1 == T3。

          另一方面,您缺少 stdlib.h 参数 - 但我认为这不太可能成为问题。

          【讨论】:

          • 您可以消除这三个类型位置中的两个,并且在类型更改时仍然是安全的:T *p; p = malloc(sizeof *p); 没有强制转换,没有类型或大小不匹配的可能性,没有错误。
          【解决方案8】:

          如果您使用 C(不是 C++)在 64 位系统上编译,它可能会引入一个错误。

          基本上,如果您忘记包含 stdlib.h,则将应用默认的 int 规则。因此编译器会很高兴地假设malloc 具有int malloc(); 的原型。在许多64 位系统上,int 是32 位,指针是64 位。

          哦,哦,值被截断了,你只能得到指针的低 32 位!现在如果你转换malloc 的返回值,这个错误被转换隐藏了。但是如果你不这样做,你会得到一个错误(类似于“无法将 int 转换为 T *”的性质)。

          这当然不适用于 C++,原因有两个。首先,它没有默认的 int 规则,其次它需要强制转换。

          总而言之,无论如何,你应该只是新的 C++ 代码:-P。

          【讨论】:

          • 如果你打开编译器警告,你会知道你忘记包含 stdlib.h 因为你会收到关于 malloc 的隐式声明的警告(以及你拥有的所有其他函数从标准库中使用)。
          【解决方案9】:

          这个问题被标记为 C 和 C++,所以它至少有两个答案,恕我直言:

          C

          咳咳……随心所欲。

          我相信上面给出的原因“如果你不包含“stdlib”那么你将不会收到警告”不是一个有效的理由,因为人们不应该依赖这种黑客来不要忘记包含一个标题.

          可能让你编写强制转换的真正原因是 C 编译器已经默默地将 void * 强制转换为你想要的任何指针类型,因此,自己做是矫枉过正和无用的.

          如果您想要类型安全,您可以切换到 C++ 或编写自己的包装函数,例如:

          int * malloc_Int(size_t p_iSize) /* number of ints wanted */
          {
             return malloc(sizeof(int) * p_iSize) ;
          }
          

          C++

          有时,即使在 C++ 中,您也必须从 malloc/realloc/free 实用程序中获利。然后你必须投。但你已经知道了。一如既往,使用 static_cast() 会比 C 风格的强制转换更好。

          在 C 中,您可以通过模板覆盖 malloc(和 realloc 等)以实现类型安全:

          template <typename T>
          T * myMalloc(const size_t p_iSize)
          {
           return static_cast<T *>(malloc(sizeof(T) * p_iSize)) ;
          }
          

          会这样使用:

          int * p = myMalloc<int>(25) ;
          free(p) ;
          
          MyStruct * p2 = myMalloc<MyStruct>(12) ;
          free(p2) ;
          

          以及以下代码:

          // error: cannot convert ‘int*’ to ‘short int*’ in initialization
          short * p = myMalloc<int>(25) ;
          free(p) ;
          

          不会编译,所以,没问题

          总而言之,在纯 C++ 中,如果有人在您的代码中发现多个 C malloc,您现在没有任何借口... :-)

          C + C++ 交叉

          有时,您希望生成可以在 C 和 C++ 中编译的代码(无论出于何种原因......这不是 C++ extern "C" {} 块的重点吗?)。在这种情况下,C++ 需要强制转换,但 C 不会理解 static_cast 关键字,因此解决方案是 C 风格的强制转换(正是由于这种原因,在 C++ 中仍然是合法的)。

          请注意,即使编写纯 C 代码,使用 C++ 编译器编译它也会得到更多警告和错误(例如,尝试使用函数而不首先声明它不会编译,这与上面提到的错误不同) .

          因此,为了安全起见,编写可在 C++ 中干净编译的代码,研究并纠正警告,然后使用 C 编译器生成最终的二进制文件。这再次意味着,以 C 风格的演员表编写演员表。

          【讨论】:

          • 在您的“C”段中,我认为您没有解决这个论点。演员不能从 void* 进行演员。它可以与您的函数执行相同的操作(防止隐式强制转换为正确的指针类型以外的任何内容)。添加演员在这方面就像使用你的功能一样。
          • 当然,类型安全是否有任何好处仍然值得商榷,因为在任何情况下都可以使用 malloc(sizeof(*p)*​​n) 而不必关心类型。
          • 使用 mallocs 需要告诉它你想要一个 int 数组(例如)。所以你必须使用 sizeof,最后,也许,添加演员表(在 C/C++ 兼容的代码中)。该函数有一点好处:1:完全静态安全,2:隐藏一些不应该出现在代码中的内部。
          • 关于类型安全的争论:不过,代码并不是注定要给“牛仔开发者”的。它是为希望从编译器获得所有可能帮助的开发人员而设计的。封装“危险代码”是朝着这个目标迈出的一步,而这个函数正是这样做的...... :-) ...
          【解决方案10】:

          “忘记stdlib.h”参数是一个稻草人。现代编译器会检测并警告问题(gcc -Wall)。

          您应该始终立即转换 malloc 的结果。不这样做应该被认为是一个错误,而不仅仅是因为它会像 C++ 一样失败。例如,如果您的目标是具有不同类型指针的机器架构,如果您不进行强制转换,最终可能会遇到一个非常棘手的错误。

          编辑:评论者Evan Teran 是正确的。我的错误是认为编译器不必在任何上下文中对 void 指针做任何工作。当我想到 FAR 指针错误时,我会发疯,所以我的直觉是强制转换所有内容。谢谢埃文!

          【讨论】:

          • 我可能是错的,但我相信 C 标准说数据指针可以安全地与任何其他数据指针相互转换。显然,函数指针是一个不同的问题。但是 malloc 总是返回一个数据指针。
          • 是的,可以施放,但不施放就不行!
          • 我认为你错了。 void * 在赋值时被隐式转换为适当的指针类型(我们都知道这一点)。如果从 malloc 的 void * 转换为 T * 是合法且有意义的,那么隐式转换应该产生相同的结果。所以你提到的“bug”不存在。
          【解决方案11】:
          #if CPLUSPLUS
          #define MALLOC_CAST(T) (T)
          #else
          #define MALLOC_CAST(T)
          #endif
          ...
          int * p;
          p = MALLOC_CAST(int *) malloc(sizeof(int) * n);
          

          或者,交替

          #if CPLUSPLUS
          #define MYMALLOC(T, N) static_cast<T*>(malloc(sizeof(T) * N))
          #else
          #define MYMALLOC(T, N) malloc(sizeof(T) * N)
          #endif
          ...
          int * p;
          p = MYMALLOC(int, n);
          

          【讨论】:

          • 这是一个不错的解决方法,但如果类型发生变化,仍然需要维护。如果可能的话,我更喜欢说“使用 C 编译器,而不是 C++ 编译器”,但这是个人意见。我确实想指出,检查编译器是否为 C++ 的正确宏是 __cplusplus,而不是 CPLUSPLUS
          【解决方案12】:

          人们已经引用了我经常提到的原因:关于不包括 stdlib.h 和使用 sizeof *p 来确保类型和大小始终匹配的旧(不再适用于大多数编译器)论点,无论以后是否更新。我确实想指出另一个反对强制转换的论点。这是一个小问题,但我认为它适用。

          C 是相当弱类型的。大多数安全类型转换是自动发生的,而大多数不安全类型转换需要强制转换。考虑:

          int from_f(float f)
          {
              return *(int *)&f;
          }
          

          那是危险的代码。它在技术上是未定义的行为,但实际上它会在几乎每个运行它的平台上做同样的事情。演员会告诉你“这段代码是一个可怕的黑客。”

          考虑:

          int *p = (int *)malloc(sizeof(int) * 10);
          

          我看到一个演员表,我想知道,“为什么这有必要?黑客在哪里?”这让我毛骨悚然,说发生了一些邪恶的事情,而实际上代码是完全无害的。

          只要我们使用 C,强制转换(尤其是指针强制转换)就是一种表达“这里发生了一些邪恶且容易破坏的事情”的方式。他们可能会完成你需要完成的事情,但他们会向你和未来的维护者表明孩子们不好。

          对每个malloc 使用强制类型转换会减少指针强制类型转换的“hack”指示。看到*(int *)&amp;f; 之类的东西就不会那么刺耳了。

          注意:C 和 C++ 是不同的语言。 C 是弱类型,C++ 是强类型。在 C++ 中,强制转换 是必要的,即使它们根本不表示 hack,因为(在我看来)C++ 类型系统过于强大。 (真的,这种特殊情况是我认为 C++ 类型系统“太强”的唯一地方,但我想不出它“太弱”的任何地方,这使得它总体上对我的口味来说太强了。)

          如果您担心 C++ 兼容性,请不要担心。如果您正在编写 C,请使用 C 编译器。每个平台都有很多非常好的可用。如果出于某种荒谬的原因,您必须编写可以干净地编译为 C++ 的 C 代码,那么您并不是真的在编写 C。如果您需要将 C 移植到 C++,您应该进行大量更改让你的 C 代码更符合 C++ 的习惯。

          如果你不能做到这些,那么无论你做什么,你的代码都不会漂亮,所以你决定如何转换并不重要。我确实喜欢使用模板创建一个返回正确类型的新分配器的想法,尽管这基本上只是重新发明了new 关键字。

          【讨论】:

            猜你喜欢
            • 2021-11-24
            • 2021-07-11
            • 1970-01-01
            • 1970-01-01
            • 2016-08-24
            • 2021-06-06
            • 2021-09-20
            相关资源
            最近更新 更多