我最近开始学习C,课程代码如下
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int a[3][4] = { {1, 2, 3, 4} , {5, 6, 7, 8}, {9, 10, 11, 12}};
int n = sizeof(a)/sizeof(a[0]); // число строк
int m = sizeof(a[0])/sizeof(a[0][0]); // число столбцов
int *final = a[0] + n*m - 1; // указатель на самый последний элемент
for(int *ptr=a[0], i=1; ptr<=final; ptr++, i++)
{
printf("%d \t", *ptr);
// если остаток от целочисленного деления равен 0,
// переходим на новую строку
if(i%m==0)
{
printf("\n");
}
}
return 0;
}
它在这里说
int *final = a[0] + n*m - 1;
这意味着最终指针必须获得二维数组的最后一个元素的地址,所以它发生了。但是我决定检查它是否适用于在指针中指定数组地址的所有方式——嗯,就是这样。
- 一个;
- 一个[0];
- &a[0][0];
最后两个问题没有问题,它们根据下面的代码将地址增加了 11 - 应该是这样,但在第一个版本中,地址全部增加了 44,这在理论上不应该。问题是为什么会发生这种情况,更有趣的是如何?如果它们都以相同的地址开头。
#include <stdio.h>
int main(void)
{
int a[3][4] = { {1, 2, 3, 4} , {5, 6, 7, 8}, {9, 10, 11, 12} };
int n = sizeof(a) / sizeof(a[0]);
int m = sizeof(a[0]) / sizeof(a[0][0]);
printf("size of a = %d\n", sizeof(a));
printf("size of a[0] = %d\n", sizeof(a[0]));
printf("size of a[0][0] = %d\n", sizeof(a[0][0]));
printf("n = %d m = %d\n\n\n", n, m);
printf("address a = %p\n", a); //адрес первого элемента массива
printf("address a[0] = %p\n", a[0]); //адрес первого элемента массива
printf("address a[0][0] = %p\n\n\n", &a[0][0]); //адрес первого элемента массива
int* fin = a + n * m - 1; //(увеличивается не на 11 адресов а на 44 хотя начальный адрес у всех одинаковый)
int* fin1 = a[0] + n * m - 1; //адрес последнего элемента массива
int* fin2 = &a[0][0] + n * m - 1; //адрес последнего элемента массива
printf("fin: address %p \t value %d\n", fin, *fin); //(увеличивается не на 11 адресов а на 44 хотя начальный адрес у всех одинаковый)
printf("fin1: address %p \t value %d\n", fin1, *fin1); //адрес последнего элемента массива
printf("fin2: address %p \t value %d\n\n\n", fin2, *fin2); //адрес последнего элемента массива
int* ptrd = a;
int diff = fin - ptrd;
printf("fin - ptrd = %d\n\n\n", diff);
for (int* ptr = a[0], i = 1; ptr <= fin2; ptr++, i++)
{
printf("i = %d ptr = %p %d ", i, ptr, *ptr);
if (i % m == 0)
{
printf("\n");
}
}
printf("\n\n");
for (int* ptr = a[0], i = 1; ptr <= fin; ptr++, i++)
{
printf("i = %d ptr = %p %d ", i, ptr, *ptr);
if (i % m == 0)
{
printf("\n");
}
}
printf("\n\n\n");
return 0;
}
指针算法是基于这样一个事实,对于
增加
p
1 意味着移动到下一个元素,即 将值增加sizeof(type)
.B
ina a[3][4]
类型a
是指向 的指针int[4]
,所以在a
添加到地址11*sizeof(int[4])=11*16=176
?那些。以字节为单位 - 偏移 176 个字节,以int
'ax (fin
- 指向int
) 的指针 - 到176/4=44
.指针算法
可以对指针进行整数值的加减运算。此类操作的含义如下。
如果
P
是指向i
元素数组的第-个n
元素(或紧跟数组的最后一个元素的假设元素)的指针,并且j
是某个整数值,则指针P + j
指向i+j
数组的第-个元素(或紧跟最后一个元素数组的假设元素),前提是0 <= i+j <= n
,否则程序的行为是未定义的。此外,指针
P - j
指向数组的i-j
第 - 个元素(或紧跟数组最后一个元素的假设元素),前提是0 <= i-j <= n
,否则程序的行为是未定义的。在给定的代码中
指针指向索引
p0
处的数组元素,指针指向索引处的数组元素。arr
0
p1
arr
1
每个数组元素
arr
占用一个sizeof(int)
字节(通常是四个字节)。虽然我们给指针加了一个,但是以字节为单位,它的值增加了一个sizeof(int)
字节。这是指针运算的一个特点。如果指针
p
类型为T*
,则表达式的结果是指向字节p + j
的p - j
“修改后的”指针。j * sizeof(T)
同样,考虑到类型的大小,计算两个指针的差值。
如果指针
P
和Q
指向同一数组的第i
ij
个元素(或紧跟数组最后一个元素的假设元素),则表达式的结果P - Q
是一个有符号整数值,等于i - j
,并且类型为ptrdiff_t
(定义在头文件<stddef.h>
)。如果
P
并且Q
不指向同一数组的元素(或紧随数组最后一个元素的假设元素),或者如果该值i-j
不能用 type 表示ptrdiff_t
,则程序的行为是未定义的。在给定的代码中
值为。
diff
_ 但是,指针和字节1
之间的差异是(通常是四个)。这是指针运算的一个特点。p1
p0
sizeof(int)
如果某个对象不是数组,那么出于指针算术的目的,它被认为是由一个元素组成的数组。
数组和指针
数组是一个独立的实体。数组不等同于指向其第一个元素的指针。
sizeof
应用于数组的运算符返回数组的大小(以字节为单位),但不返回指向数组第一个元素的指针的大小。假设有一个
n
类型为 的元素数组T
。&
应用于此类数组的地址获取运算符返回指向n
类型元素T
数组的指针,即 指针是类型T (*)[n]
但是应用于数组第一个元素的地址运算符返回一个指向 的指针T
,即 指针是类型T*
。指向整个数组的指针和指向数组第一个元素的指针表示相同的地址但具有不同的类型。这直接影响指针算术,因为 它取决于指针指向的类型的大小。
在某些情况下,数组可以隐式转换为指向其第一个元素的指针。
例如,如果一个数组参与了指针运算,那么它会被隐式转换为指向其第一个元素的指针。
int* p1 = arr + 1
- 数组被arr
隐式转换为指向其第一个元素(索引为零)的指针,因此arr + 1
- 这是指向数组第二个元素(索引为一)的指针arr
。ptrdiff_t diff1 = p1 - arr
- 数组arr
再次隐式转换为指向其第一个元素的指针,因此不同p1 - arr
之处在于指向同一数组中具有索引的元素的指针之间的差异1
和0
,因此表达式的结果p1 - arr
是1
。ptrdiff_t diff0 = arr - arr
- 同样,数组被arr
转换为指向其第一个元素的指针。多维数组
k
维数组k > 1
是一个一维数组,其元素是k-1
维数组。在上面的代码
arr
中,是семи
类型元素的一维数组,T1
其中是类型元素的一维数组,其中是类型元素的一维数组,其中是。T1
четырёх
T2
T2
трёх
T3
T3
int
arr
有类型int [7][4][3]
。arr[0]
有类型int [4][3]
。arr[0][0]
有类型int [3]
。arr[0][0][0]
有类型int
。有了上面写的所有内容,您的代码
工作如下。
表达式
&a[0][0]
的类型是int*
。该表达式&a[0][0] + 11
意味着向int*
指向对象的指针arr[0][0]
添加一个字节одиннадцать
。sizeof(int)
表达式
a[0]
的类型为int [4]
,即 这是一个数组。由于将整数值添加到数组中,因此数组被隐式转换为指向其第一个元素的指针,即 到指针int*
。因此,表达式a[0] + 11
意味着在int*
指向 object的指针上arr[0][0]
增加одиннадцать
一个sizeof(int)
字节。表达式
a
的类型为int [3][4]
,即 这是一个数组。由于将整数值添加到数组中,因此数组被隐式转换为指向其第一个元素的指针,即 到指针int (*)[4]
。(是的,数组元素a
是数组,而不是int
's)。因此,表达式a + 11
意味着在int (*)[4]
指向 object的指针上arr[0]
增加одиннадцать
一个sizeof(int[4]) == 4 * sizeof(int)
字节。关于数组溢出
在答案的开头,它说如果表达式
P + j
,其中P
是指针并且j
是整数,不指向数组元素(或数组最后一个元素之后的假设元素),那么程序的行为未定义。上面的代码包含未定义的行为:
数组
a
正好包含три
type的元素int [4]
,所以表达式a + 11
不指向数组的元素a
,也不引用数组之外的假设元素,因此程序的行为是未定义的。此外,下面的代码还包含未定义的行为:
表达式
&a[0][0]
指向由type 元素arr[0]
组成的数组的第一个元素,因此表达式不指向数组的元素,也不指向数组之外的假设元素,因此程序的行为是未定义的。четырёх
int
&a[0][0] + 11
arr[0]
是的,我们知道一个数组后面紧跟着一个
arr[0]
数组arr[1]
,然后一个数组后面紧跟着一个数组arr[2]
,但是,语言标准并没有对这种情况做出任何让步。在该站点上,关于允许超出多维数组的子数组的讨论已经破坏了相当多的副本。没有单一的答案。主要有两个职位:
相关问题:可以将多维数组视为一维吗?
Pro
printf
和转换说明符使用转换说明符,您承诺将
printf
某种类型的参数传递给函数。你必须信守诺言。如果您不这样做,那么语言标准就其本身而言,对您没有任何承诺 - 程序的行为没有定义。转换说明符
%d
表示必须传递类型参数int
,但运算符sizeof
返回无符号整数类型的值size_t
。程序的行为是未定义的。使用说明符%zu
输出 type 的值size_t
。转换说明符
%p
表示应该传递一个类型的参数void*
,但是传递的参数(既不是a
、也不是a[0]
、也不是&a[0][0]
)都不是指定的类型,并且在这种特殊情况下不会隐式转换为指定的类型。程序的行为是未定义的。使用显式强制转换:(void*)a
,(void*)a[0]
,(void*)&a[0][0]
.相关问题:printf 和 pointers。
cppreference.com
printf
上关于转换说明符的大表函数的文章:https ://en.cppreference.com/w/c/io/fprintf 。