移动应用开发实验室2019级面试题
1. define中#, ## 的区别
定义明示常量#define 中
#是实现将其与其后后宏参数 转变成 字符串 的功能
宏参数只能在#后,#宏参数 → “宏参数” (字符串标志:双引号)
##是实现将两个宏参数黏合在一起变成 标识符 的功能
宏参数可在前,可在后,也都可在
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
替换文本随后被插入到程序中原来文本的位置.对于宏,参数名被他们的值替换。
最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
字符串是有自动连接的特点的
只有当字符串作为宏参数的时候才可以把字符串放在字符串中
使用 # ,可以把一个宏参数变成对应的字符串
使用 ## ,可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
可变宏: …和 _VA_ARGS _
可用在替换部分中,_VA_ARGS _
表示省略号代表什么
嵌套宏的展开规律:
一般展开规律为:先展开参数,再分析函数,由内向外展开
当宏中有#运算符的时候,不展开参数
当宏中有##运算符的时候,先展开函数,再分析参数
例如如下代码:
#include <stdio.h>
#define Xname(n) X##n // 确定宏参数n,便可将X与n粘合成Xn,Xn此时就可以单独地作为一个标识符使用
#define Print(n) printf("The value of X" #n " is %d.\n",X#n)
// #n就会被替换成字符串"n"与字符串"The value of X"和字符串" is %d.\n"拼接在一起
// X##n 此时就是标识符Xn
int main()
{
int Xname(1) = 10;
int Xname(2) = 20;
int X3 = 30;
Print(1);
Print(2);
Print(3);
}
输出结果:
The value of X1 is 10.
The value of X2 is 20.
The value of X3 is 30.
对于#、##来说,如果参数是宏的话会展开吗:不会
#include <stdio.h>
#include <string.h>
#define STRCPY(a,b) strcpy(a##_p,#b)
int main()
{
char var1_p[20];
char var2_p[30];
strcpy(var1_p,"aaa");
strcpy(var2_p,"bbb");
STRCPY(STRCPY(var1,var2),var2); // !!!!
puts(var1_p);
puts(var2_p);
return 0;
}
选择gcc -E让其停止在预处理阶段
先是无法成功编译,给出这条语句不合法
展开的结果不是strcpy(strcpy(var1_p,“var2”)_p,“var2”);
而是strcpy(STRCPY(var1,var2)_p,“var2”)
说明 ## 阻止了参数的宏展开
如果宏参数里没有用到 # 和 ##, 宏将会完全展开
##还具有去除空格进行黏接的作用:
#define A1(name, type) type name_ ##type ##_type
等价于
#define A1(name, type) type name_##type##_type
若A1(ans,int)
, 会转变成int name_int_type
而不是 int ans_int_int
拓展
宏定义函数与普通函数有什么区别?
1.宏定义函数没有参数检验
预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。
2.代码编译生成的指令不同
宏定义没有调用和传参指令,多的是每次调用都会编译生成一个类似的函数体
3.宏定义函数应当尤其注意括号问题
括号问题很重要,尤其当参数是表达式的时候,所以我们能添加括号的地方就添加括号
4.注意参数是表达式的问题
参数是表达式括号一定要带上
参数是表达式时有可能会多计算表达式
5.一些宏函数会导致底的代码执行效率
举个简单的例子,有些宏函数可能会重复计算, 不但会出错也会导致效率降低
#define Max(a, b) a > b ? a : b
Max(++a, ++b)
结果是++a > ++b ? ++a : ++b
但是宏定义函数看起来像函数,但是却不会引起函数调用引来的额外时间开销。
多次调用函数,函数宏就有了优势,函数宏带来空间上增大(直接文本替换)但没有函数调用来回时间,是以空间换时间的做法
所有宏函数在c里有好处,但是也要注意使用,最好(一定)避免表达式传参
2.接受不论什么类型的参数并返回 integer 结果的函数–空指针实现
实例:stdlib.h库中的qsort函数参数中的的函数指针指向的compare函数的两个参数:
/*qsort()原型:
*在stdlib.h中
* void qsort (void *base, size_t nmemb, size_t size, int (*compare)(const void*, const void*))
*
* 第一个参数是空指针(指向待排序数组的首元素)因此可以指向任何类型的数组
* 第二个参数是待排序项的数量
* 第三个参数是数组中每个元素的大小,由于第一个参数是void指针,qsort函数不知道数组中每个元素的大小,可用sizeof(type_name)表示大小
* 第四个参数是一个指向函数compare(需另外定义)的指针,该函数接受两个参数:分别指向比较两项的指针。
* compare()函数的原型如下:
*
* int (*compare)(const void*, const void*)
*
* */
/* 2.qsorter.c 用qsort函数排序一组数字 */
#include <stdio.h>
#include <stdlib.h>
#define NUM 50
void fillarray(double ar[], int n);
void showarray(const double ar[], int n);
int mycomp(const void*, const void*);
int main()
{
double vals[NUM];
fillarray(vals, NUM);
puts("排序前:");
showarray(vals, NUM);
qsort(vals, NUM, sizeof(double), mycomp);
puts("\n排序后:");
showarray(vals, NUM);
}
/* 按从小到大的顺序 */
int mycomp(const void *p1, const void *p2){
const double *a1 = (double*)p1;
const double *a2 = (double*)p2;
if(*a1 > *a2) /* 这里可以理解成使*a1-*a2,若大于0返回1,小于0,返回-1,两数相等时返回0 */
return 1;
else if (*a1 == *a2)
return 0;
else
return -1;
}
void fillarray(double ar[], int n){
int index;
for(index = 0; index < n; index++)
ar[index] = (double)rand() / ((double)rand() + 0.1);
}
void showarray(const double ar[], int n){
int i;
for(i=0;i<n;i++){
printf("%9.4f ",ar[i]);
if(i%6==5)
putchar('\n');
}
if(i%6!=0)
putchar('\n');
}
代码中的这一部分:
/* 按从小到大的顺序 */
int mycomp(const void *p1, const void *p2){
const double *a1 = (double*)p1; // 对空指针进行强制类型转化 赋给一个非空指针
const double *a2 = (double*)p2;
/* 在非空指针存放的 空指针存的地址 上(the address which store in the pointer point the void pointer )代替空指针操作 */
if(*a1 > *a2)
return 1;
else if (*a1 == *a2)
return 0;
else
return -1;
}
关于空指针
详见文章末尾,void指针二三事
3.1
int x,y,m,n;
x = 0;
y = 1;
m = x, y;
n = (x, y);
printf("%d ", m);
printf("%d\n", n);
输出结果:
0 1
考点:
逗号运算符,赋值运算符,括号运算符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqV08amc-1587826457828)(https://s1.ax1x.com/2020/04/06/Gyxs6e.jpg)]
分析:
逗号运算符,优先级别最低,双目运算符,运算顺序从左向右,但最后返回值是整个逗号表达式是最后一个表达式的值
m = x, y; 相当于(m=x),y;
赋值运算符的运算优先级是大于逗号运算符优先级的,因此先执行m=x运算,m被赋于0
再执行y表达式
n = (x, y);
括号优先级又高于赋值号优先级,因此先执行括号内语句,括号内逗号表达式的运算结果(返回值)是y的值 1
,再将 1
赋于n
因此最终结果为 0 1
3.2与3.5一起讲
int x=3;
int y=4;
int a = x++ + x++ + x++;
int b = ++y + ++y + ++y;
printf("%d, %d\n", a, b);
输出结果:
12 , 19
分析:
对于自增/自减的后缀运算符,返回值返回自增/自减前的数值,前缀运算符反之,但结束该语句后,下一次用到上次进行过自增/自减运算符的变量是自增/自减后的值。
++ x(即增量运算符)使用“先更改后使用”原理,而x ++(即增量运算符)则使用“先使用再更改”原理。
本题一共有三种运算符,赋值运算符 =
自增运算符 ++
二元加法运算符 +
~~优先级 ++
(后缀) > ++
(前缀)> +
> =~~
错解:
那么对于a来说,自左向右:
x++执行,返回3,但此时x=4
x++执行,返回4,但此时x=5
x++执行,返回5,但此时x=6
a=3+4+5
a=12
对于b:
++y执行,返回5,此时y=5
++y执行,返回6,此时y=6
++y执行,返回7,此时y=7
b=5+6+7
b=18****
以上划线解释全错!
知识点讲解:求值顺序–3.5
正解:
像上述表达式是未定义的!
证据一:https://zh.cppreference.com/w/c/language/eval_order
证据二:C Primer Plus 中文译本 P 527 下半部分
证据三:以下来自C和指针 5.4.4优先级和计值顺序章节,中文译本在P 82下半部分
解决方案:
int x=3;
int y=4;
int a = x++;
a+=x++;
a+=x++;
int b = ++y ;
b+=++y;
b+=++y;
printf("%d, %d\n", a, b);
输出结果:
3.3与3.4
struct node
{
int x; int y; int z;
};
struct one a = { 1, 10, 100 };
struct node *p = &a;
printf("%d\n", *(int*)p);
输出结果:
1
考点:
字节对齐:下一题 or https://shimo.im/docs/ydYJdGQqkHhV6ckP/
强制类型转换
大小端
分析:
对齐数:Linux下为4 Windows 的 VS 下为 8 (不一定,可通过#pragma pack(x)
控制) ,第二个成员大小为4 对齐数取4
Linux下为4 Windows 的 VS 下为 8(不一定,可通过#pragma pack(x)
控制) ,第三个成员大小为4 对齐数取4
3.4:
struct B{
int a;
char b;
double c;
};
struct A{
char a;double b;
int c;
};
struct C{
char a;
double b;
struct A c;
int d;
};
int main(){
printf("%d,%d\n",sizeof(struct A),sizeof(struct B));
printf("%d",sizeof(struct C));
}
输出结果:
#pragma pack(4)
16,16
32
#pragma pack(4)
24 16
48
考点:字节对齐
转换规则:
-
第一个成员在与结构体变量偏移量为0的地址处。
-
其他的每一个成员变量(非嵌套结构体)要对齐到某个数字(对齐数)的整数倍的地址处。
当成员是数组时,该成员的对齐数是一个数组元素的大小
如果嵌套了结构体的情况,嵌套的结构体对齐到自己成员的最大对齐数的整数倍处。(就算自己成员对齐数都小于编译器默认对齐数也不取编译器默认对齐数而取成员对齐数中最大的)
对齐数 = 编译器默认的一个对齐数 与 该成员大小 的较小值。
VS中默认的值为8 Linux中的默认值为4(这个不一定 可以用#pragma pack(x) 修改x确定编译器默认对齐数x 默认参数只能设置成1,2,4,8,16)
-
结构体总大小为所有成员中的最大对齐数的整数倍。
如果这个结构体成员中含嵌套结构体的话,嵌套结构体作为结构体成员取嵌套体内成员最大对齐数作为嵌套结构体对齐数,那么结构体总大小为这些对齐数中最大的那个整数倍
解析:
对于结构体 sizeof A、B的求值是同理的
#pragma pack(8)
对于结构体内嵌套结构体的struct C来说,其大小为:48
3.5
Don’t even try to find out how your compiler implements such things, let alone write code which depends on them. AS Kernighan and Ritchie(K&R标准作者) wisely point out, ‘‘if you don’t know how they are done on various machines, that innocence may help to protect you.’’(傻傻的简单的单独用带副作用运算符)
int i = 1;
printf("%d %d %d", i ,++i,i++);
输出结果
3 3 1
解题
副作用和序列点
副作用和序列点
我们再讨论一个C语言的术语副作用(side effect)。副作用是对数据对象或文件的修改。例如,语句:
states = 50;
它的副作用是将变量的值设置为50。副作用?这似乎更像是主要目的! 但是从C语言的角度看,主要目的是对表达式求值。给出表达式4 + 6,C会对其求值得10;给出表达式states = 50,C会对其求值得50。对该表达式求值的副作用是把变量states的值改为50。跟赋值运算符一样,递增和递减运算符也有副作用,使用它们的主要目的就是使用其副作用。类似地,调用 printf()函数时,它显示的信息其实是副作用(printf()的返回值是待显示字符的个数)。序列点(sequence point)是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。在 C语言中,语句中的分号标记了一个序列点。意思是**,在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成**。后面我们要讨论的一些运算符也有序列点。另外,任何一个完整表达式的结束也是一个序列点。
什么是完整表达式?所谓完整表达式(full expression),就是指这个表达式不是另一个更大表达式的子表达式。例如,表达式语句中的表达式和while循环中的作为测试条件的表达式,都是完整表达式。序列点有助于分析后缀递增何时发生。例如,考虑下面的代码:while(guests++ < 10)
printf("%d\n",guests);对于该例,C语言的初学者认为“先使用值,再递增它”的意思是,在printf()语句中先使用guests,再递增它。但是,表达式guests++ < 10是一个完整的表达式,因为它是while循环的测试条件,所以该表达式的结束就是一个序列点。因此,C 保证了在程序转至执行 printf()之前发生副作用(即,递增guests)。同时,使用后缀形式保证了guests在完成与10的比较后才进行递增。
现在,考虑下面这条语句:
y = (4 + x++) + (6 + x++);
表达式4 + x++不是一个完整的表达式,所以C无法保证x在子表达式4 +x++求值后立即递增x。这里,完整表达式是整个赋值表达式语句,分号标记了序列点。所以,C 保证程序在执行下一条语句之前递增x两次。C并未指明
是在对子表达式求值以后递增x,还是对所有表达式求值后再递增x。因此,要尽量避免编写类似的语句。
分析:
求值顺序 https://zh.cppreference.com/w/c/language/eval_order
除下列标出者,任意 C 运算符的运算数求值顺序,包括函数调用表达式的函数参数求值顺序,及任何表达式的子表达式求值顺序都是未指定的。编译器会以任意顺序对其求值,而且在同一表达式被再度求值时可选用另一种顺序。
C 中没有从左到右或从右到左求值的概念,这不会与运算符的从左到右或从右到左结合性混淆:表达式 f1() + f2() + f3()
被分析成 (f1() + f2()) + f3()
,因为 operator+ 的从左到右结合性,但运行时对 f3
的函数调用可以最先、最后,或在 f1()
与 f2()
之间求值。
定义
求值
对于每个表达式或子表达式,有二种求值为编译器进行(两者都是可选的):
- 值计算( value computation ):计算表达式所返回的值。这可以涉及到确定对象身份(左值求值)或读取之前赋给对象的值(右值求值)
- 副效应( side effect ):访问(读或写)以 volatile 左值指代的对象、修改(写)对象、原子同步 (C11 起)、修改文件、修改浮点环境(若支持)或调用进行上述操作的函数。
若表达式不产生副效应,且编译器能确定其值不被使用,则表达式可以不求值。
排序
“先序于( sequenced before )”是一种同一线程内求值的不对称、传递性、成对的关系(若引入原子类型和内存屏障,则它可以扩展到线程间)。
- 若在子表达式 E1 和 E2 间存在序列点( sequence point ),则 E1 的值计算和副效应都先序于 E2 的所有值计算和副效应
若求值 A 先序于求值 B,则求值 A 将在求值 B 开始前完成。若求值 A 不先序于求值 B,且求值 B 先序于求值 A,则求值 B 将在求值 A 开始前完成。若求值 A 不先序于求值 B,且求值 B 不先序于求值 A ,则存在二种可能:求值 A 与 B 是无顺序( unsequenced )的:它们可以以任意顺序进行,并且可以重叠(在执行的单一线程中,编译器可以交错地写入构成 A 与 B 的 CPU 指令)求值 A 与 B 是非确定顺序( indeterminably-sequenced )的:它们可以以任意顺序进行,但不可重叠:要么是 A 将在 B 之前完成,要么是 B 在 A 之前完成。顺序可以在相同表达式的下次求值前相反。 | (C11 起) |
---|---|
规则
-
在所有函数参数和函数指代器的求值后,实际调用函数前,有一个序列点。
-
在下例二元运算符(++这种是一元,不适用)的第一(左)运算数求值后,第二(右)运算数求值前,有一个序列点:
&&
(逻辑与)、||
(逻辑或),及,
(逗号)。 -
在条件运算符
?:
的第一(左)运算数求值后,第二或第三运算数(无论何者被求值)前,有一个序列点。 -
在完整表达式(非子表达式的表达式:典型的是以分号为结尾者或 if/switch/while/do 的控制语句)的求值后,下个完整表达式前,有一个序列点。
5) 在完整声明器的结尾,有一个序列点。6) 在紧接库函数返回前,有一个序列点。7) 在格式化 I/O 中,关联到每个转换指定符的动作后,有一个序列点(特别是 scanf 写入同一变量的不同域, printf 读取并以多于一次使用 %n 修改同一变量是良式的)。8) 在每次通过库函数 qsort 和 bsearch 调用比较函数前和紧随其后有序列点,在 qsort 调用比较函数和移动对象之间也有序列点。 | (C99 起) |
---|---|
9) 属于运算符的运算数的值计算(但非副效应)先序于运算符的值计算(但非其副效应)。10) 直接赋值运算符与所有复合赋值运算符的副效应(修改左参数)后序于左右参数的值计算(但非其副效应)。11) 后自增和后自减运算符的值计算先序于其副效应。12) 既非先序于亦非后序于另一函数调用的函数调用是非确定顺序的(构成不同函数调用的 CPU 指令不可能交错,即使函数被内联)。13) 在初始化器列表表达式中,所有求值都是非确定顺序的14) 考虑到非确定顺序的函数调用,复合赋值运算符,及自增减运算符的前后缀形式都是单独求值。 | (C11 起) |
未定义行为
- 若对一个标量对象的副效应与另一个对同一标量对象的副效应相对无顺序,则行为未定义。
i = ++i + i++; // 未定义行为
i = i++ + 1; // 未定义行为
f(++i, ++i); // 未定义行为
f(i = -1, i = -1); // 未定义行为
- 若一个标量对象上的副效应与另一个使用同一标量对象之值的值计算相对无顺序,则行为未定义。
f(i, i++); // 未定义行为
a[i] = i++; // 未定义行为
- 只要至少一个子表达式的排序容许这种无顺序副效应,就应用上述规则。
参考资料
首先我们先明白一个点,C语言中有许多行为是未定义的。
C语言中会有三个这样类似的点implementation-defined, unspecified, and undefined behavior.
首先我们先明确一点,参数入栈顺序和参数计值顺序无关
再一个,参数计值顺序是未定义的 undefined behavior (UB)
https://stackoverflow.com/questions/22616986/order-of-evaluation-of-arguments-in-function-calling
What the C standard says is that the C standard itself does not define the order of evaluation. Some secondary standard is still free to do so.
Incidentally, this does not by itself involve undefined behavior. There are cases where the unspecified order of evaluation can lead to undefined behavior, for example:
printf("%d %d\n", i++, i++); /* undefined behavior! */
it is UB because you don’t know the order of increments and the time of storing the incremented values. Assuming
i==0
prior to the instruction all results (0,0), (0,1) and (1,0) are possible.
a[i]=i++;
printf("%d%d",i++,i--);
这样的都是编译器未定义的,引用implementation-defined, unspecified, and undefined behavior.
的话就是
ignore (that is, avoid worrying about generating correct code for) certain marginal constructs which are too difficult to define precisely and which probably aren’t useful to well-written programs anyway
对于未定义的行为编译器可为了可移植性进行自己规定,编译器也可选择忽略这样的未定义(尽管可能引发考虑代码是否正确),原因是这样的代码一是难以被人/编译器定义,不同人也有不同观点,并且这对编写好的代码也没什么用
implementation-defined: The implementation must pick some behavior; it may not fail to compile the program. (The program using the construct is not incorrect.) The choice must be documented. The Standard may specify a set of allowable behaviors from which to choose, or it may impose no particular requirements.
unspecified: Like implementation-defined, except that the choice need not be documented.
undefined: Anything at all can happen; the Standard imposes no requirements. The program may fail to compile, or it may execute incorrectly (either crashing or silently generating incorrect results), or it may fortuitously do exactly what the programmer intended.
Note, too, that since the Standard imposes absolutely no requirements on the behavior of a compiler faced with an instance of undefined behavior, the compiler (more importantly, any generated code) can do absolutely anything. In particular, there is no guarantee that at most the undefined bit of the program will behave badly, and that the rest of the program will perform normally. It’s perilous to think that you can tolerate undefined behavior in a program, imagining that its undefinedness can’t hurt; the undefined behavior can be more undefined than you think it can. (See question 3.2 for a relatively simple example.)
Since many people seem to have trouble comprehending the depths to which undefined behavor can descend, it is traditional to come up with eye-catching, outrageous examples. Undefined means that, notwithstanding question 9.2,
printf("%d", j++ <= j);
can print 42, or ``forty-two.’’If you’re interested in writing portable code, you can ignore the distinctions, as you’ll usually want to avoid code that depends on any of the three behaviors.
See also questions 3.9, and 11.34.
(A fourth defined class of not-quite-precisely-defined behavior, without the same stigma attached to it, is locale-specific.)
总之:不要写类似的代码,也不好移植
This is another clone of What does an expression involving multiple post/pre decrement/increment operators evaluate to in C and C++?
Expressions that modify the same variable twice without sequencing are not supported by C (and C++). They have no meaning in those two programming languages (formally, Undefined behavior). When a program attempts to execute such expression, its behavior is undefined: from the point of C, a program that executes printf ("%d %d %d", i,i++, ++i)
is exactly the same program as one that attempts division by zero, null pointer dereference, or buffer overflow. If you get any output, it is meaningless garbage that varies with compiler vendor, compiler version, target platform, compilation flags, surrounding code, and other factors.
There is a summary of the rules for C: Order of evaluation and for C++: Order of evaluation
Modern compilers diagnose this error
gcc: [Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
rog.c:5:32: warning: operation on 'i' may be undefined [-Wsequence-point] printf ("%d %d %d", i,i++, ++i); ^~~prog.c:5:32: warning: operation on 'i' may be undefined [-Wsequence-point]
clang: [Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
prog.c:5:28: warning: unsequenced modification and access to 'i' [-Wunsequenced] printf ("%d %d %d", i,i++, ++i); ~ ^1 warning generated.
The specific garbage these compilers printed when the error was ignored was 3 2 3
in one and 1 1 3
in the other. And when I tried another compiler (C (vc) - rextester ), I got 2 2 2
. All three compilers are correct.
参考回答:
一、
Under my compiler, the code
int i = 7;
printf("%d\n", i++ * i++);
prints 49. Regardless of the order of evaluation, shouldn’t it print 56?
对于
++
,-
,=
,+ =
,-=
等这样的运算符都是unspecified的 会有side effects(附带作用)
二、
“Is printf sensitive to the order of arguments?”
3.6
printf("g%c",printf("%d",printf("XY!")));
输出结果:
XY!3gctrl^A
ctrl^A是ASCII 中 1
对应的字符,是不可打印字符
考点:
printf返回值
printf()函数原型:int printf (char* format, args, …)
返回值为传到输出流的字符数(不包含终止字符NULL(’\0’))
On success, the total number of characters written is returned. If a writing error occurs, the error indicator (ferror) is set and a negative number is returned. If a multibyte character encoding error occurs while writing wide characters, errno is set to EILSEQ and a negative number is returned.
If successful, the total number of characters written is returned. On failure, a negative number is returned.
解析:
嵌套括号先从最内层括号开始执行,先printf("XY!")
打印出XY!
,返回值3
,再执行printf("%d",3)
,打印出3
,返回值为1
,再执行printf("g%c",1);
,打印出g
和ctrl^A
3.7
struct perInfo1{
int num;
char name[10];
double account;
};
struct perInfo2{
char name[10];
double account;
int num;
};
int main()
{
struct perInfo1 Tony1 = {1,"Tony",3.14159};
int structSize1 = sizeof(struct perInfo1);
struct perInfo2 Tony2 = {"Tony",3.14159,1};
int structSize2 = sizeof(struct perInfo2);
printf("Size : { int, char[10], double } = %d\n",structSize1);
printf("Size : { char[10], double, int } = %d\n",structSize2);
return 0;
}
输出结果:
当最前加#pragma pack(8) 让编译器默认对齐数为8:
Size : { int, char[10], double } = 24
Size : { char[10], double, int } = 32
当最前加#pragma pack(4) 让编译器默认对齐数为4:
Size : { int, char[10], double } = 24
Size : { char[10], double, int } = 24
解析:
当编译器默认对齐数为8:#pragma pack(8)
字节对齐总结
https://shimo.im/docs/ydYJdGQqkHhV6ckP/
3.8
int main()
{
int a[4] [3]={1,2,3,4,5,6,7,8,9,10,11,12};
// 假设 a的首地址为0x0000000000000000
printf("%p,%p\n",a,a+1);
printf("%p,%p\n",a[0],a[0]+1);
printf("%p,%p\n",*a,*a+1);
printf("%d\n",a[0] [0]);
printf("%d,%d\n",*a[0],*(a[0]+1));
printf("%d\n",**a);
printf("%d\n",* (*(a+3)+1));
}
输出结果:
0x0000000000000000 0x000000000000001c
0x0000000000000000 0x0000000000000004
0x0000000000000000 0x0000000000000004
1
1,2
1
11
考点:
指针的加减,数组
分析:
运算符[ ]相当于*( + )
a[i][j]
==*(*(a+i)+j)
void指针二三事
一个空指针指向的数据类型没有与任何数据类型有所联系,它可以存储任何数据类型的地址,也可以被转化成任何数据类型的指针。
int a = 10;
char b = 'x';
void *p = &a; // void pointer holds address of int 'a'
p = &b; // void pointer holds address of char 'b'
空指针的特点:
1)malloc()和calloc()函数会返回void*的指针,并且这些函数指针对应的地址还被允许存储分配给任意的数据类型
int main(void)
{
// Note that malloc() returns void * which can be
// typecasted to any type like int *, char *, ..
int *x = malloc(sizeof(int) * n);
}
以上代码仅在C中可被编译,C++中必须在malloc函数前加上申请空间所存储的数据类型的指针"(int *)",进行指针基类型的强制转换,而这一点对于C来说不是必须的。
2)空指针在C语言中可被实现泛型函数(泛型函数就是你定义函数时候,是万能类型。在调用的时候,只要你把具体的类型传进去就好。好处呢,就是代码的复用,减少代码量。),例如用在stdlib.h头文件中的qsort函数的compare函数。(C Primer Plus 中文译本第六版 P552)
一些有趣的事实:
1)你定义好的空指针不能被间接引用(即 *指针名),如下的代码是不能被编译的
#include<stdio.h>
int main()
{
int a = 10;
void *ptr = &a;
printf("%d", *ptr);
return 0;
}
输出:
Compiler Error: 'void*' is not a pointer-to-object type
如下的代码可是有被编译的:
#include<stdio.h>
int main()
{
int a = 10;
void *ptr = &a;
printf("%d", *(int *)ptr); // 进行强制类型转换
return 0;
}
输出
10
2)C标准不允许指针与空指针间进行算数运算。然而,在GNU C中,这被允许但是空指针的大小被规定为1。 在gcc编译器下运行如下代码:
#include<stdio.h>
int main()
{
int a[2] = {1, 2};
void *ptr = &a;
ptr = ptr + sizeof(int);
printf("%d", *(int *)ptr);
return 0;
}
输出:
2
在其他编译器下可能不可被编译成功。
具体相关的空指针运算的说明:
https://stackoverflow.com/questions/20967868/should-the-compiler-warn-on-pointer-arithmetic-with-a-void-pointer
You are not supposed to do pointer arithmetic on void pointers.
From the C Standard
6.5.6-2: For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to an object type and the other shall have integer type.
6.2.5-19: The void type comprises an empty set of values; it is an incomplete type that cannot be completed.
以上代码均通过gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)
编译运行