我只学了不到一个星期的C(在我的C++和其他语言的帮助下),我对指针和它们的声明方式感到困惑。
下面,我使用一个名为 Object 的简单结构:
struct Object { int id; };
下面的创建指针的方法是否只是以不同的方式做同样的事情,或者没有?
struct Object obj1 = { .id = 1 };
struct Object* obj1_p = &obj1; // method 1 of getting a pointer
// The same, just in a compound literal?
struct Object* obj2_p = &(struct Object){ .id = 1 }; // method 2 of getting a pointer
// Is this the same, other than being uninitialized?
struct Object* obj3_p = malloc(sizeof(struct Object)); // method 2 of getting a pointer
有没有一个时候,你只能用一种方法?
另外,作为一个侧记,为什么有些人投malloc,它是更好地这样做?
// malloc is casted to object:
struct Object* obj3_p = (Object*)malloc(sizeof(struct Object));
4条答案
按热度按时间kpbwa7wx1#
这两个“方法”做的是完全相同的事情。正如你所说,第二个只是compound literal。
这将为
struct Object
分配足够的内存,而无需初始化它。而不是don't need to cast it,因为malloc
返回void *
,它会自动安全地提升到任何其他指针。但是如果你这样做,你应该把它转换为struct Object*
而不是Object*
。不过,看起来很笨重...我最喜欢的方法是这样的:
w41d8nur2#
我写了这段代码,希望它能帮助你更好地理解指针的一些特性:
这是注解掉未定义行为时的输出:
我建议你看看这些链接,它们对我很有帮助:
8wigbo563#
你的问题有两个不同的主题。
1.指针变量定义
你只能用一种方式定义指针变量:
1.将该值赋给指针变量。此值应引用与指针类型相同的有效对象或大小不小于指针类型的有效内存。示例中的不同之处在于如何创建引用对象。
有没有一个时候,你只能用一种方法?
这取决于程序逻辑。你只需要记住底层对象的作用域,以避免取消引用不存在于特定作用域之外的对象:
另外,作为一个侧记,为什么有些人投malloc,它是更好地这样做?
这被认为是一种不好的做法,因为如果没有
malloc
的原型,它会使警告静音。最好不要投。现代编译器和最新的C标准不允许使用没有原型的函数最好使用
sizeof(object)
而不是sizeof(type)
,就好像你改变了对象的类型,你需要改变程序中sizeof(type)
的所有出现。它很容易错过一些,很难发现错误。rhfm7lfc4#
首先,让我们整理一些术语-
你用完全相同的方式 * 声明 * 所有三个指针:
唯一的区别是如何 * 初始化 * 它们。
C语言中的声明有两个主要组成部分-一个 * 声明说明符 * 序列,后面是一个逗号分隔的可选初始化 * 声明符 * 列表。C++声明的结构基本上是相同的,但对于这个答案,我将坚持使用C术语(因为这是我们谈论的语言,我更熟悉)。
声明说明符包括存储类说明符(
auto
、static
、register
、typedef
等)、类型说明符(int
、char
、double
、short
等)、类型限定符(const
、volatile
、restrict
)等。声明符包括被声明的对象的名称,沿着有关其指针性、数组性和/或函数性的信息。
标量对象的初始化器是标量。聚合对象(如数组、结构和联合)的初始化器是用大括号括起来的列表,或者在字符数组的情况下是字符串。
宣言中
struct Object
,* 声明符 * 是* obj1_p
,* 初始化器 * 是= &obj_1
。我知道C声明指针对象的约定是
T* p
,但语法实际上是T (*p)
-*
操作符总是绑定到声明符,而不是类型说明符。如果你写T* p, q;
,那么只有p
被声明为指向T
的指针;q
被声明为T
的示例。我知道为什么C约定存在,我知道它的理由,但它确实歪曲了C和C++中声明语法的工作方式,我认为使用它是一个错误。大多数C程序员将使用T *p
约定。下面是C中指针声明的基本规则:
const
的规则是:这三种方法的不同之处在于如何初始化指针。
方法1只是获取先前声明的相同类型的变量的地址:
方法2是获取一个 * 复合字面值 * 的地址-基本上,一个匿名变量:
obj1
和匿名对象之间的唯一区别是,你可以直接引用obj1
,也可以通过指针引用:而你只能通过指针变量引用匿名对象
方法3动态分配内存并分配结果对象的地址(如果
malloc
调用失败,则可能是NULL
)。这个方法和其他两个方法的主要区别是,
malloc
分配的内存一直挂起,直到您显式地free
它,无论obj3_p
变量是否超出范围。如果obj1
和匿名对象在一个块中声明,并且没有static
关键字,那么当包含它们的块退出时,该内存会自动释放。另外,作为一个侧记,为什么有些人投malloc,它是更好地这样做?
有两种情况下你必须转换
malloc
(以及calloc
和realloc
)的结果:1.您正在将代码编译为C++;
1.您正在使用一个 * 古老 * 的、C89之前的K&R实现。
与C不同,C不允许
void *
(从malloc
返回的类型)和其他指针类型之间的隐式转换。您必须显式地将转换为void *
或从void *
转换。话虽如此,如果你正在编写C,你不应该直接调用malloc
。你应该使用一个容器来管理内存(std::string
、std::vector
、std::map
等),或者你应该使用new
或new []
操作符。如果你正处于从C到C的提升和转换过程中,在你可以重写内存管理代码之前,保留malloc
调用是可以接受的,但是理想情况下,C代码不应该直接使用malloc
(或者calloc
或realloc
)。在最早的C语言版本中,
malloc
、calloc
和realloc
返回char *
,所以如果你要将结果赋给不同类型的指针,你必须对结果进行强制转换:作为一个在大学里写K&R C的人,这是一个痛苦的屁股。这是混乱和错误的不断来源。如果您更改了
p
的类型(例如从int *
更改为long *
),则必须在多个位置重复该更改。这造成了更高的维护负担,特别是如果(通常情况下)指针声明与malloc
调用被其他代码分开:在C99之前,你必须在任何语句之前声明所有变量(无论如何,在那个块中),所以指针声明通常通过多行代码与
malloc
调用分开。在声明中更改*p
的类型真的很容易,但在后来的赋值中却忘记了,这会导致微妙的(有时不那么微妙的)运行时错误。1989年的标准引入了
void
类型,并将*alloc
函数改为返回void *
。它还引入了一条规则,即您可以将void *
值分配给其他指针类型,反之亦然,而无需显式强制转换。因此,您可以将malloc
调用编写为:或
如果您更改
*p
的类型,则只需在一个位置进行更改。它更干净,更难搞砸,等等。此外,在C89标准下,如果您忘记包含
stdlib.h
或在作用域中没有malloc
的声明,则强制转换malloc
的结果可能会抑制有用的编译器诊断。但是由于C99取消了隐式的int
声明2,这不再是一个真正的问题。然而,有些人出于各种原因更喜欢保持明确的演员阵容。就我个人而言,我发现这些原因缺乏,我已经积累了足够的疤痕组织从坏石膏,我宁愿离开他们完全关闭。30多年的代码编写经验让我相信,在内存管理方面,越简单越好。使用它们并不会造成伤害;它们不会减慢代码或类似的东西。但从可读性和维护的Angular 来看,强制转换
malloc
是不好的。1.声明中的空格只有在分隔标记时才有意义。
*
字符本身是一个标记,不是任何标识符的一部分,因此您可以写入T *p
、T* p
、T*p
或T * p
中的任何一个,它们都将被解析为T (*p)
。1.在C99之前,如果编译器看到一个没有前面声明的函数调用,它会假设函数返回
int
,所以如果你忘记包含stdlib.h
,编译器会在你试图将malloc
的结果赋值给指针时抱怨,因为int
不能隐式转换为指针。但是,如果你使用石膏,那么诊断将被抑制。