C语言中不必要的指针转换

时间:2020-03-06 14:29:19  来源:igfitidea点击:

我对此线程的回答有一个评论:

函数调用中的Malloc似乎在返回时被释放?

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

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

我有此评论:

Can I just say, please don't cast the
  return value of malloc? It is not
  required and can hide errors.

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

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

似乎双方都有很好的论据。伙计们,感谢发帖。

解决方案

强制转换返回(void *)而不是(int *)的函数是无害的:我们正在将一种类型的指针转​​换为另一种类型的指针。

强制转换返回整数而不是指针的函数很可能是错误的。如果我们未显式转换它,则编译器会标记为它。

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

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

因此,通过投射,我们一无所获,并冒着压制合法警告的风险。

关于这一点的文章很多,快速的Google搜索将显示更多详细的解释。

编辑

有人认为

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

当我们意外分配的内存不足时,这很明显,因为我们认为p是TYPe而不是TYPE,因此我们应该强制转换malloc,因为此方法的优势会覆盖意外抑制编译器警告的较小开销。

我想指出两点:

  • 我们应该编写p = malloc(sizeof(* p)* n);来确保我们正确分配了足够的空间
  • 使用上述方法,如果要更改p的类型,则需要在3个地方进行更改:一次在声明中,一次在malloc中,一次在强制转换中。

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

一个可能的错误(取决于这是否是我们真正想要的)可能是用一种大小比例进行malloc分配,并分配给另一种类型的指针。例如。,

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

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

好吧,我认为这是完全相反的,总是直接将其强制转换为所需的类型。在这里阅读!

另一方面,如果我们需要将代码移植到C ++,则最好使用" new"运算符。

实际上,强制转换隐藏错误的唯一方法是,如果我们正在从一种数据类型转换为较小的数据类型并且丢失了数据,或者我们是将梨转换为苹果。请看以下示例:

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指针,除非我们对它进行了强制转换,否则我们将无法使用它,并且大多数编译器会在我们强制转换为lvalue时向我们发出警告(分配的左侧)无论如何都不能参加。

我认为我们应该进行强制转换。请考虑类型的三个位置:

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

两行代码可能被广泛分开。因此,编译器将强制执行T1 == T2是一件好事。直观地验证T2 == T3更容易。

如果我们错过了T2演员表,那么我们必须希望T1 == T3.

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

它可能引起的一种错误是,如果我们正在使用C(而不是C ++)在64位系统上进行编译。

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

呃,值被截断了,我们只能得到指针的低32位!现在,如果我们强制转换malloc的返回值,则此错误将被强制转换隐藏。但是,如果不这样做,我们将得到一个错误(某种性质是"无法将int转换为T *")。

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

总而言之,无论如何,我们都应该是C ++代码中的新手:-P。

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

天哪...随便你。

我相信上面给出的"如果我们不包括" stdlib",那么我们将不会得到警告"的理由不是一个有效的理由,因为人们不应该依靠这种黑客手段来忘记包括标题。

可能导致我们不编写强制转换的真正原因是C编译器已经将void *静默转换为所需的任何指针类型,因此,自己执行此操作是多余的且无用的。

如果要具有类型安全性,则可以切换到C ++或者编写自己的包装函数,例如:

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

有时,即使在C ++中,我们也必须从malloc / realloc / free utils中获利。然后,我们必须进行投射。但是我们已经知道了。像往常一样,使用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 ++extern" C" {}块的意义吗?)。在这种情况下,C ++需要强制转换,但是C无法理解static_cast关键字,因此解决方案是C样式强制转换(出于这种原因,这在C ++中仍然合法)。

请注意,即使编写纯C代码,使用C ++编译器进行编译也会为我们带来更多警告和错误(例如,与上述错误不同,尝试在未先声明函数的情况下使用该函数将无法编译)。

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

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

我们应该始终立即转换malloc的结果。不这样做将被视为错误,而不仅仅是因为它将作为C ++失败。例如,如果我们要针对具有不同类型指针的机器体系结构,那么如果不进行强制转换,可能会遇到一个非常棘手的错误。

编辑:评论员埃文·特兰(Evan Teran)是正确的。我的错误是认为编译器无需在任何上下文中对void指针进行任何工作。当我想到FAR指针错误时,我很害怕,所以我的直觉是投下所有东西。谢谢埃文!

#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);

人们已经引用了我通常不推荐使用的原因:关于不包含stdlib.h的旧的(不再适用于大多数编译器)论点,并使用sizeof * p来确保类型和大小始终匹配,无论以后是否进行更新。我确实要指出反对铸造的另一种论点。它很小,但是我认为它适用。

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

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

那是危险的代码。从技术上讲,这是未定义的行为,尽管实际上,它将在运行它的几乎每个平台上执行相同的操作。演员表会"此代码是很糟糕的事情"。

考虑:

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

我看到一个演员,我纳闷:"为什么要这样做?骇客在哪里?"实际上,代码完全无害,这使我脖子上的头发有些恶作剧。

只要我们使用C,强制转换(尤其是指针强制转换)就是一种说法,"这里发生了一些邪恶的事情,很容易破坏。"他们可能会完成我们需要完成的任务,但会向我们和将来的维护者表明孩子们还不行。

在每个malloc上使用强制转换会减少指针强制转换的" hack"指示。这样可以减少类似*(int *)&f;之类的东西的震撼。

注意:C和C ++是不同的语言。 C是弱类型,C ++是强类型。在C ++中,强制转换是必需的,即使它们根本不表示被黑客入侵,也是因为(以我的拙见)由于不必要的强大的C ++类型系统。 (确实,这种情况是我认为C ++类型系统"太强大"的唯一地方,但是我无法想到它"太弱"的任何地方,这会使它总体上太强大而无法满足我的口味。)

如果我们担心C ++的兼容性,请不必担心。如果我们正在编写C,请使用C编译器。每个平台都有很多非常好的产品。如果出于某种愚蠢的原因,我们必须编写可以像C ++一样干净地编译的C代码,那么我们实际上并不是在编写C。如果需要将C移植到C ++,则应该进行大量更改以使C代码更加惯用C ++。

如果我们不能执行任何操作,那么无论我们执行什么操作,代码都不会很漂亮,因此,决定何时进行强制转换并不重要。我确实喜欢使用模板来创建一个返回正确类型的新分配器的想法,尽管这基本上只是在重新发明new关键字。