use*_*784 81 c arrays pointers
int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
Run Code Online (Sandbox Code Playgroud)
导致段错误,而
int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
Run Code Online (Sandbox Code Playgroud)
没有.现在:
int *nums = {5, 2, 1, 4};
printf("%d\n", nums);
Run Code Online (Sandbox Code Playgroud)
打印5.
基于此,我猜想数组初始化符号{}会盲目地将这些数据加载到左边的任何变量中.当它是int []时,数组将根据需要填充.当它是int*时,指针被填满5,并且存储指针之后的存储器位置被填充为2,1和4.因此nums [0]尝试deref 5,导致段错误.
如果我错了,请纠正我.如果我是正确的,请详细说明,因为我不明白为什么数组初始化程序按照它们的方式工作.
Lun*_*din 113
C中有一条(愚蠢的)规则,说任何普通变量都可以用括号括起的初始化列表初始化,就好像它是一个数组一样.
例如,你可以写int x = {0};
,这完全等同于int x = 0;
.
因此,当您编写int *nums = {5, 2, 1, 4};
时,实际上是将初始化列表提供给单个指针变量.但是,它只是一个变量,因此它只会被赋予第一个值5,列表的其余部分被忽略(实际上我不认为具有多余初始化程序的代码甚至应该使用严格的编译器进行编译) - 它不会完全写入记忆.代码相当于 int *nums = 5;
.这意味着,nums
应该指向地址 5
.
此时您应该已经有两个编译器警告/错误:
当然,代码会崩溃并烧毁,因为5
很可能不是您可以取消引用的有效地址nums[0]
.
作为旁注,您应该printf
使用说明%p
符指针地址,否则您将调用未定义的行为.
我不太确定你在这里尝试做什么,但是如果你想设置一个指向数组的指针,你应该这样做:
int nums[] = {5, 2, 1, 4};
int* ptr = nums;
// or equivalent:
int* ptr = (int[]){5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)
或者,如果要创建指针数组:
int* ptr[] = { /* whatever makes sense here */ };
Run Code Online (Sandbox Code Playgroud)
编辑
经过一些研究,我可以说"多余元素初始化列表"确实无效C - 它是GCC扩展.
标准6.7.9初始化说(强调我的):
2 初始化程序不应尝试为未初始化的实体中包含的对象提供值.
/ - /
11 标量的初始值设定项应为单个表达式,可选择用大括号括起来.对象的初始值是表达式的初始值(转换后); 与简单赋值相同的类型约束和转换适用,将标量的类型作为其声明类型的非限定版本.
"标量类型"是一个标准术语,指的是不属于数组,结构或联合类型的单个变量(称为"聚合类型").
因此,在简单的英语中,标准说:"当你初始化变量时,可以随意在初始化表达式周围抛出一些额外的括号,只因为你可以."
art*_*rtm 28
情景1
Run Code Online (Sandbox Code Playgroud)int *nums = {5, 2, 1, 4}; // <-- assign multiple values to a pointer variable printf("%d\n", nums[0]); // segfault
为什么这是一个段错误?
你声明nums
为int的指针 - 它nums
应该保存内存中一个整数的地址.
然后,您尝试初始化nums
为多个值的数组.因此,在不深入细节的情况下,这在概念上是不正确的 - 将多个值分配给应该包含一个值的变量是没有意义的.在这方面,如果你这样做,你会看到完全相同的效果:
int nums = {5, 2, 1, 4}; // <-- assign multiple values to an int variable
printf("%d\n", nums); // also print 5
Run Code Online (Sandbox Code Playgroud)
在任何一种情况下(为指针或int变量分配多个值),接下来会发生变量将获得第一个值5
,而忽略其余值.此代码符合但您会收到不应该在赋值中的每个附加值的警告:
warning: excess elements in scalar initializer
.
对于为指针变量分配多个值的情况,程序在您访问时会发生段错误nums[0]
,这意味着您将从字面上引用存储在地址5中的任何内容.nums
在这种情况下,您没有为指针分配任何有效内存.
值得注意的是,对于为int变量分配多个值的情况没有段错误(这里没有解除引用任何无效指针).
情景2
int nums[] = {5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)
这个不会出现段错误,因为您在合法分配堆栈中包含4个整数的数组.
情景3
int *nums = {5, 2, 1, 4};
printf("%d\n", nums); // print 5
Run Code Online (Sandbox Code Playgroud)
这个不会像预期的那样发生段错误,因为你正在打印指针本身的值 - 而不是它解除引用的内容(这是无效的内存访问).
其他
每当你对像这样的指针的值进行硬编码时,它几乎总是注定会发生段错误(因为确定哪个进程可以访问哪个内存位置是操作系统任务).
int *nums = 5; // <-- segfault
Run Code Online (Sandbox Code Playgroud)
因此,经验法则是始终初始化指向某个已分配变量地址的指针,例如:
int a;
int *nums = &a;
Run Code Online (Sandbox Code Playgroud)
要么,
int a[] = {5, 2, 1, 4};
int *nums = a;
Run Code Online (Sandbox Code Playgroud)
M.M*_*M.M 25
int *nums = {5, 2, 1, 4};
是不正确的代码.有一个GCC扩展,它将此代码视为:
int *nums = (int *)5;
Run Code Online (Sandbox Code Playgroud)
试图形成一个指向内存地址5的指针.(这对我来说似乎不是一个有用的扩展,但我想开发人员基地想要它).
为了避免这种行为(或者至少得到警告),你可以在标准模式下编译,例如-std=c11 -pedantic
.
另一种有效代码形式是:
int *nums = (int[]){5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)
它指向相同存储持续时间的可变文字nums
.但是,int nums[]
版本通常更好,因为它使用更少的存储空间,您可以使用它sizeof
来检测数组的长度.
Gop*_*opi 12
int *nums = {5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)
nums
是一个类型的指针int
.所以你应该指出一些有效的内存位置.num[0]
您试图取消引用一些随机内存位置,从而导致分段错误.
是的,指针保持值为5,您试图取消引用它,这是系统上未定义的行为.(看起来5
不是您系统上的有效内存位置)
而
int nums[] = {1,2,3,4};
Run Code Online (Sandbox Code Playgroud)
是一个有效的声明,你说的nums
是一个类型的数组,int
并根据初始化期间传递的元素数分配内存.
Fah*_*qui 10
通过分配 {5, 2, 1, 4}
int *nums = {5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)
你正在分配5 nums
(在从int到指向int的隐式类型转换之后).取消它会对内存位置进行访问调用0x5
.您的程序可能无法访问.
尝试
printf("%p", (void *)nums);
Run Code Online (Sandbox Code Playgroud)