如何在C中声明指针

lnlaulya  于 2023-10-16  发布在  其他
关注(0)|答案(4)|浏览(88)

我只学了不到一个星期的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));
kpbwa7wx

kpbwa7wx1#

这两个“方法”做的是完全相同的事情。正如你所说,第二个只是compound literal

struct  Object obj1 = { .id = 1 };
struct  Object *obj1_p = &obj1;

// The same, just in a compound literal?
struct  Object *obj2_p = &(struct Object){ .id = 1 };

这将为struct Object分配足够的内存,而无需初始化它。而不是don't need to cast it,因为malloc返回void *,它会自动安全地提升到任何其他指针。但是如果你这样做,你应该把它转换为struct Object*而不是Object*

struct Object *obj3_p = (struct Object*) malloc(sizeof(struct Object));

不过,看起来很笨重...我最喜欢的方法是这样的:

struct Object *obj3_p = malloc(sizeof *obj3_p);
w41d8nur

w41d8nur2#

我写了这段代码,希望它能帮助你更好地理解指针的一些特性:

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

struct Object { int id; };

struct Object *getObjectNaive() {
    struct Object* obj2_p = &(struct Object) { .id = 2 };

    return obj2_p; // UB: Returns the address of a local object (the compound literal).
}

struct Object *getObject() {
    struct Object* obj3_p = malloc(sizeof(*obj3_p)); // Better way of calling malloc than using sizeof(struct Object).
    obj3_p->id = 3; // You don't need to do this.

    return obj3_p; // This needs to be freed later on!
}

int main(void) {
    struct Object obj1 = { .id = 1 };
    struct Object* obj1_p = &obj1;
    
    printf("obj1.id = %d\n", obj1_p->id); 
    obj1_p->id = 10; // You can change values using the pointer
    printf("obj1.id = %d\n", obj1_p->id); 

    // The only different thing with this case is that you don't
    // "lose" your object when setting the pointer to NULL 
    // (although you can only access it through the object, not through the pointer).

    obj1_p = NULL;
    printf("obj1.id = %d\n", obj1_p->id); // This won't work (undefined behaviour).
    printf("obj1.id = %d\n", obj1.id); // This will.

    struct Object* obj2_p = &(struct Object) { .id = 1 };
    obj2_p->id = 2; // You can change the id
    printf("obj2.id = %d\n", obj2_p->id);

    // If you make this pointer point to another address, you "lose" your object.
    obj2_p = NULL;
    printf("obj2.id = %d", obj2_p->id); // This won't work at all (undefined behaviour).

    // Both of these pointers point to objects in the stack, so, for example,
    // they don't work when returning from a function.
    obj2_p = getObjectNaive();
    obj2_p->id = 20; // This won't work (undefined behaviour).
    printf("obj2.id = %d\n", obj2_p->id); // This works if you don't dereference the pointer.

    // The third case is not the same as the other two, since you are allocating memory on the heap.
    // THIS is a time where you can only use one of these three methods.
    struct Object *obj3_p = getObject(); // This works!
    printf("obj3.id = %d\n", obj3_p->id);
    obj3_p->id = 30; // This works now.
    printf("obj3.id = %d\n", obj3_p->id);

    free(obj3_p); // You need to do this if you don't want memory leaks.

    return 0;
}

这是注解掉未定义行为时的输出:

obj1.id = 1
obj1.id = 10
obj1.id = 10
obj2.id = 2
obj2.id = 2
obj3.id = 3
obj3.id = 30

我建议你看看这些链接,它们对我很有帮助:

8wigbo56

8wigbo563#

你的问题有两个不同的主题。

struct  Object* obj1_p = .......; 
^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^
pointer object           initialization
definition

1.指针变量定义
你只能用一种方式定义指针变量:

type *objectname;

1.将该值赋给指针变量。此值应引用与指针类型相同的有效对象或大小不小于指针类型的有效内存。示例中的不同之处在于如何创建引用对象。
有没有一个时候,你只能用一种方法?
这取决于程序逻辑。你只需要记住底层对象的作用域,以避免取消引用不存在于特定作用域之外的对象:

struct Object *valid1(void)   //valid
{
    struct Object* obj3_p = malloc(sizeof(*obj3_p)); 

    return obj3_p;
}

struct  Object obj1 = { .id = 1 };
struct Object *valid2(void)   // valid
{
    struct Object* obj3_p = &obj1; 

    return obj3_p;
}

struct Object *invalid1(void)   // invalid
{
    struct  Object obj1 = { .id = 1 };
    struct Object* obj3_p = &obj1; 

    return obj3_p;
}

struct Object *invalid2(void)   // invalid
{
    struct Object* obj3_p = &(struct Object){ .id = 1 };

    return obj3_p;
}

另外,作为一个侧记,为什么有些人投malloc,它是更好地这样做?
这被认为是一种不好的做法,因为如果没有malloc的原型,它会使警告静音。最好不要投。现代编译器和最新的C标准不允许使用没有原型的函数
最好使用sizeof(object)而不是sizeof(type),就好像你改变了对象的类型,你需要改变程序中sizeof(type)的所有出现。它很容易错过一些,很难发现错误。

rhfm7lfc

rhfm7lfc4#

首先,让我们整理一些术语-
你用完全相同的方式 * 声明 * 所有三个指针:

struct Object* objn_p ...

唯一的区别是如何 * 初始化 * 它们。
C语言中的声明有两个主要组成部分-一个 * 声明说明符 * 序列,后面是一个逗号分隔的可选初始化 * 声明符 * 列表。C++声明的结构基本上是相同的,但对于这个答案,我将坚持使用C术语(因为这是我们谈论的语言,我更熟悉)。
声明说明符包括存储类说明符(autostaticregistertypedef等)、类型说明符(intchardoubleshort等)、类型限定符(constvolatilerestrict)等。
声明符包括被声明的对象的名称,沿着有关其指针性、数组性和/或函数性的信息。
标量对象的初始化器是标量。聚合对象(如数组、结构和联合)的初始化器是用大括号括起来的列表,或者在字符数组的情况下是字符串。
宣言中

struct Object* obj1_p = &obj_1;
  • 声明说明符 * 是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中指针声明的基本规则:

Declaration Declarator
Specifier
----------- ----------
T                  *p;   // p is a pointer to T
T               *a[N];   // a is an array of pointer to T
T                *f();   // f is a function returning a pointer to T
T             (*a)[N];   // a is a pointer to an array of T
T              (*f)();   // f is a pointer to a function returning T

const的规则是:

T const *p;  // p points to a const T
const T *p;  // same as above
T * const p; // p is a const pointer to T

这三种方法的不同之处在于如何初始化指针。
方法1只是获取先前声明的相同类型的变量的地址:

struct Object *obj1_p = &obj1; // using the C convention for pointer declarations

方法2是获取一个 * 复合字面值 * 的地址-基本上,一个匿名变量:

struct Object *obj2_p = &(struct Object){ .id = 1 };

obj1和匿名对象之间的唯一区别是,你可以直接引用obj1,也可以通过指针引用:

printf( "%d %d %d", obj1.id, obj1_p->id, (*obj1_p).id );

而你只能通过指针变量引用匿名对象

printf( "%d %d", obj2_p->id, (*obj2_p).id );

方法3动态分配内存并分配结果对象的地址(如果malloc调用失败,则可能是NULL)。

struct Object *obj3_p = malloc( sizeof( struct Object ) );

这个方法和其他两个方法的主要区别是,malloc分配的内存一直挂起,直到您显式地free它,无论obj3_p变量是否超出范围。如果obj1和匿名对象在一个块中声明,并且没有static关键字,那么当包含它们的块退出时,该内存会自动释放。
另外,作为一个侧记,为什么有些人投malloc,它是更好地这样做?
有两种情况下你必须转换malloc(以及callocrealloc)的结果:
1.您正在将代码编译为C++;
1.您正在使用一个 * 古老 * 的、C89之前的K&R实现。
与C不同,C不允许void *(从malloc返回的类型)和其他指针类型之间的隐式转换。您必须显式地将转换为void *或从void *转换。话虽如此,如果你正在编写C,你不应该直接调用malloc。你应该使用一个容器来管理内存(std::stringstd::vectorstd::map等),或者你应该使用newnew []操作符。如果你正处于从C到C的提升和转换过程中,在你可以重写内存管理代码之前,保留malloc调用是可以接受的,但是理想情况下,C代码不应该直接使用malloc(或者callocrealloc)。
在最早的C语言版本中,malloccallocrealloc返回char *,所以如果你要将结果赋给不同类型的指针,你必须对结果进行强制转换:

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

作为一个在大学里写K&R C的人,这是一个痛苦的屁股。这是混乱和错误的不断来源。如果您更改了p的类型(例如从int *更改为long *),则必须在多个位置重复该更改。这造成了更高的维护负担,特别是如果(通常情况下)指针声明与malloc调用被其他代码分开:

int *p = NULL;
...
p = (int *) malloc( sizeof *p * N );

在C99之前,你必须在任何语句之前声明所有变量(无论如何,在那个块中),所以指针声明通常通过多行代码与malloc调用分开。在声明中更改*p的类型真的很容易,但在后来的赋值中却忘记了,这会导致微妙的(有时不那么微妙的)运行时错误。

1989年的标准引入了void类型,并将*alloc函数改为返回void *。它还引入了一条规则,即您可以将void *值分配给其他指针类型,反之亦然,而无需显式强制转换。因此,您可以将malloc调用编写为:

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

int *p = NULL;
...
p = malloc( sizeof *p * N );

如果您更改*p的类型,则只需在一个位置进行更改。它更干净,更难搞砸,等等。
此外,在C89标准下,如果您忘记包含stdlib.h或在作用域中没有malloc的声明,则强制转换malloc的结果可能会抑制有用的编译器诊断。但是由于C99取消了隐式的int声明2,这不再是一个真正的问题。
然而,有些人出于各种原因更喜欢保持明确的演员阵容。就我个人而言,我发现这些原因缺乏,我已经积累了足够的疤痕组织从坏石膏,我宁愿离开他们完全关闭。30多年的代码编写经验让我相信,在内存管理方面,越简单越好。使用它们并不会造成伤害;它们不会减慢代码或类似的东西。但从可读性和维护的Angular 来看,强制转换malloc是不好的。
1.声明中的空格只有在分隔标记时才有意义。*字符本身是一个标记,不是任何标识符的一部分,因此您可以写入T *pT* pT*pT * p中的任何一个,它们都将被解析为T (*p)
1.在C99之前,如果编译器看到一个没有前面声明的函数调用,它会假设函数返回int,所以如果你忘记包含stdlib.h,编译器会在你试图将malloc的结果赋值给指针时抱怨,因为int不能隐式转换为指针。但是,如果你使用石膏,那么诊断将被抑制。

相关问题