C语言中一个关于指针传递的问题
c语言函数指针传递
c语言函数指针传递C语言函数指针传递函数指针是C语言中非常重要的概念之一,它可以使得程序更加灵活和可扩展。
函数指针的传递是指将一个函数的指针作为参数传递给另一个函数,使得后者可以使用前者所指向的函数。
在C语言中,函数指针的类型与被指向的函数的类型是一致的。
可以使用typedef关键字来定义函数指针类型,以提高代码的可读性。
例如,可以使用以下方式定义一个函数指针类型:typedef void (*FuncPtr)(int);其中,FuncPtr是函数指针类型的名称,void是被指向的函数的返回类型,而(int)是被指向的函数的参数列表。
函数指针的传递可以使得程序更加灵活,可以根据实际需要动态地改变要执行的函数。
例如,可以定义一个函数,该函数接受一个函数指针作为参数,并在函数内部调用该指针所指向的函数。
这样,在调用该函数时,可以传入不同的函数指针,从而实现不同的功能。
这种方式常常被用于回调函数的实现。
回调函数是指当某个事件发生时,系统或程序会调用预先注册的函数,以完成特定的任务。
通过函数指针的传递,可以将回调函数的实现与事件的触发进行解耦,使得系统或程序的设计更加灵活和可扩展。
除了回调函数,函数指针的传递还可以用于其他一些场景。
例如,可以将一个函数指针作为参数传递给另一个函数,以实现函数的动态调用。
在这种情况下,函数指针可以作为一个函数库的接口,从而使得函数库的用户可以根据实际需要选择要调用的函数。
函数指针的传递还可以用于实现函数的多态性。
多态性是指同一操作作用于不同的对象上时,可以有不同的解释和执行方式。
通过将不同的函数指针传递给同一个函数,可以使得该函数在不同的情况下具备不同的行为。
需要注意的是,在使用函数指针传递时,需要确保传递的函数指针所指向的函数与接收参数的函数的参数列表和返回类型是一致的。
否则,在函数调用时可能会出现编译错误或运行时错误。
函数指针的传递是C语言中一种非常重要和常用的技术。
C语言指针经典练习题-及答案
C语言指针练习题及答案一、选择题1. 变量的指针,其含义是指该变量的____.a)值b)地址c)名d)一个标志2.若有语句int *point,a=4;和point=&a;下面均代表地址的一组选项是__ _.a)a,point,*&a b)&*a,&a,*pointc)*&point,*point,&a d)&a,&*point ,point3.若有说明;int *p,m=5,n;以下正确的程序段的是________.a)p=&n; b)p=&n;scanf("%d",&p); scanf("%d",*p);c)scanf("%d",&n); d)p=&n;*p=n; *p=m;4. 以下程序中调用scanf函数给变量a输入数值的方法是错误的,其错误原因是__ _____.main(){int *p,*q,a,b;p=&a;printf(“input a:”);scanf(“%d”,*p);……}a)*p表示的是指针变量p的地址b)*p表示的是变量a的值,而不是变量a的地址c)*p表示的是指针变量p的值d)*p只能用来说明p是一个指针变量5. 已有变量定义和函数调用语句:int a=25; print_value(&a); 下面函数的正确输出结果是______.void print_value(int *x){ printf(“%d\n”,++*x);}a)23 b)24 c)25 d)266.若有说明:long *p,a;则不能通过scanf语句正确给输入项读入数据的程序段是A) *p=&a;scanf("%ld",p);B) p=(long *)malloc(8);scanf("%ld",p);C) scanf("%ld",p=&a);D) scanf("%ld",&a);7.有以下程序#include<stdio.h>main(){ int m=1,n=2,*p=&m,*q=&n,*r;r=p;p=q;q=r;printf("%d,%d,%d,%d\n",m,n,*p,*q);}程序运行后的输出结果是A)1,2,1,2 B)1,2,2,1C)2,1,2,1 D)2,1,1,28. 有以下程序main(){ int a=1, b=3, c=5;int *p1=&a, *p2=&b, *p=&c;*p =*p1*(*p2);printf("%d\n",c);}执行后的输出结果是A)1 B)2 C)3 D)49. 有以下程序main(){ int a,k=4,m=4,*p1=&k,*p2=&m;a=p1==&m;printf("%d\n",a);}程序运行后的输出结果是()A)4 B)1 C)0 D)运行时出错,无定值10. 在16位编译系统上,若有定义int a[]={10,20,30}, *p=&a;,当执行p++;后,下列说法错误的是()A)p向高地址移了一个字节B)p向高地址移了一个存储单元C)p向高地址移了两个字节D)p与a+1等价11.有以下程序段int a[10]={1,2,3,4,5,6,7,8,9,10},*p=&a[3], b; b=p[5]; b中的值是()A)5 B)6 C)8 D)912.若有以下定义,则对a数组元素的正确引用是_________.int a[5],*p=a;a)*&a[5] b)a+2 c)*(p+5) d)*(a+2)13.若有以下定义,则p+5表示_______.int a[10],*p=a;a)元素a[5]的地址b)元素a[5]的值c)元素a[6]的地址d)元素a[6]的值14.设已有定义: int a[10]={15,12,7,31,47,20,16,28,13,19},*p; 下列语句中正确的是()A) for(p=a;a<(p+10);a++);B) for(p=a;p<(a+10);p++);C) for(p=a,a=a+10;p<a;p++);D) for(p=a;a<p+10; ++a);15.有以下程序段#include <stdio.h>int main(){ int x[] = {10, 20, 30};int *px = x;printf("%d,", ++*px); printf("%d,", *px);px = x;printf("%d,", (*px)++); printf("%d,", *px);px = x;printf("%d,", *px++); printf("%d,", *px);px = x;printf("%d,", *++px); printf("%d\n", *px);return 0;}程序运行后的输出结果是( )A)11,11,11,12,12,20,20,20 B)20,10,11,10,11,10,11,10C)11,11,11,12,12,13,20,20 D)20,10,11,20,11,12,20,2016.设有如下定义则程序段的输出结果为int arr[]={6,7,8,9,10};int *ptr;ptr=arr;*(ptr+2)+=2;printf ("%d,%d\n",*ptr,*(ptr+2));A)8,10 B)6,8 C)7,9 D)6,1017.若有定义:int a[]={2,4,6,8,10,12},*p=a;则*(p+1)的值是_4__. *(a+5)的值是__12__.18.若有以下说明和语句,int c[4][5],(*p)[5];p=c;能正确引用c数组元素的是___ __.A) p+1 B) *(p+3) C) *(p+1)+3 D) *(p[0]+2))19.若有定义:int a[2][3],则对a数组的第i行j列元素地址的正确引用为__ __.a)*(a[i]+j) b)(a+i) c)*(a+j) d)a[i]+j20.若有以下定义:int a[2][3]={2,4,6,8,10,12};则a[1][0]的值是_8_. *(*(a+1)+0)的值是_ _8.21.有以下定义char a[10],*b=a; 不能给数组a输入字符串的语句是()A)gets(a) B)gets(a[0]) C)gets(&a[0]); D)gets(b);22.下面程序段的运行结果是___ __.char *s="abcde";s+=2;printf("%d",s);a)cde b)字符'c' c)字符'c'的地址d)无确定的输出结果23.以下程序段中,不能正确赋字符串(编译时系统会提示错误)的是()A) char s[10]="abcdefg"; B) char t[]="abcdefg",*s=t;C) char s[10];s="abcdefg"; D) char s[10];strcpy(s,"abcdefg");24.设已有定义: char *st="how are you"; 下列程序段中正确的是()A) char a[11], *p; strcpy(p=a+1,&st[4]);B) char a[11]; strcpy(++a, st);C) char a[11]; strcpy(a, st);D) char a[], *p; strcpy(p=&a[1],st+2);25.有以下程序输出结果是()main(){char a[]="programming",b[]="language";char *p1,*p2;p1=a;p2=b;for(i=0;i<7;i++)if(*(p1+i)==*(p2+i))printf("%c",*(p1+i));}A)gm B)rg C)or D)ga26.设p1和p2是指向同一个字符串的指针变量,c为字符变量,则以下不能正确执行的赋值语句是_____.a)c=*p1+*p2; b)p2=c c)p1=p2 d)c=*p1*(*p2);27.以下正确的程序段是____.a)char str[20]; b)char *p;scanf("%s",&str); scanf("%s",p);c)char str[20]; d)char str[20],*p=str;scanf("%s",&str[2]); scanf("%s",p[2]);28.若有说明语句则以下不正确的叙述是____.char a[]="It is mine";char *p="It is mine";a)a+1表示的是字符t的地址b)p指向另外的字符串时,字符串的长度不受限制c)p变量中存放的地址值可以改变d)a中只能存放10个字符29.下面程序的运行结果是___.#include <stdio.h>#include <string.h>main(){ char *s1="AbDeG";char *s2="AbdEg";s1+=2;s2+=2;printf("%d\n",strcmp(s1,s2));}a)正数b)负数c)零d)不确定的值30.有以下程序运行后的输出结果是____。
c语言值传递和引用传递
c语言值传递和引用传递
在C语言中,函数参数传递通常采用值传递方式,而不是引用传递。
值传递是指在函数调用时,将实际参数的值复制一份传递给形式参数,函数中对形式参数的修改不会影响实际参数的值。
这是因为C语言中的函数参数传递是通过栈内存实现的,实际参数和形式参数分别存储在不同的内存区域中,修改形式参数不会影响实际参数。
例如,以下代码演示了值传递的示例:
```c
include <>
void modify(int x) {
x = x + 1;
}
int main() {
int a = 5;
modify(a);
printf("%d\n", a); // 输出 5,modify函数不会影响a的值
return 0;
}
```
然而,如果希望在函数中修改实际参数的值,可以将实际参数的地址作为形式参数传递给函数。
这样,函数可以通过指针访问实际参数的内存地址,从而修改其值。
例如:
```c
include <>
void modify(int x) {
x = x + 1;
}
int main() {
int a = 5;
modify(&a);
printf("%d\n", a); // 输出 6,modify函数通过指针修改了a的值
return 0;
}
```
总结来说,C语言中的函数参数传递默认采用值传递方式,但如果需要修改实际参数的值,可以将实际参数的地址作为形式参数传递给函数,从而实现引用传递的效果。
C语言数组参数与指针参数
C语言数组参数与指针参数我们都知道参数分为形参和实参。
形参是指声明或定义函数时的参数,而实参是在调用函数时主调函数传递过来的实际值。
一、一维数组参数1、能否向函数传递一个数组?看例子:void fun(char a[10]){char c = a[3];}intmain(){char b[10] = “abcdefg”;fun(b[10]);return 0;}先看上面的调用,fun(b[10]);将b[10]这个数组传递到fun 函数。
但这样正确吗?b[10]是代表一个数组吗?显然不是,我们知道b[0]代表是数组的一个元素,那b[10]又何尝不是呢?只不过这里数组越界了,这个b[10]并不存在。
但在编译阶段,编译器并不会真正计算b[10]的地址并取值,所以在编译的时候编译器并不认为这样有错误。
虽然没有错误,但是编译器仍然给出了两个警告:warning C4047: 'function' : 'char *' differs in levels of indirection from 'char 'warning C4024: 'fun' : different types for formal and actual parameter 1这是什么意思呢?这两个警告告诉我们,函数参数需要的是一个char*类型的参数,而实际参数为char 类型,不匹配。
虽然编译器没有给出错误,但是这样运行肯定会有问题。
如图:这是一个内存异常,我们分析分析其原因。
其实这里至少有两个严重的错误。
第一:b[10]并不存在,在编译的时候由于没有去实际地址取值,所以没有出错,但是在运行时,将计算b[10]的实际地址,并且取值。
这时候发生越界错误。
第二:编译器的警告已经告诉我们编译器需要的是一个char*类型的参数,而传递过去的是一个char 类型的参数,这时候fun 函数会将传入的char 类型的数据当地址处理,同样会发生错误。
理解C语言(一)数组、函数与指针
理解C语⾔(⼀)数组、函数与指针1 指针⼀般地,计算机内存的每个位置都由⼀个地址标识,在C语⾔中我们⽤指针表⽰内存地址。
指针变量的值实际上就是内存地址,⽽指针变量所指向的内容则是该内存地址存储的内容,这是通过解引⽤指针获得。
声明⼀个指针变量并不会⾃动分配任何内存。
在对指针进⾏间接访问前,指针必须初始化: 要么指向它现有的内存,要么给它分配动态内存。
对未初始化的指针变量执⾏解引⽤操作是⾮法的,⽽且这种错误常常难以检测,其结果往往是⼀个不相关的值被修改,并且这种错误很难调试,因⽽我们需要明确强调: 未初始化的指针是⽆效的,直到该指针赋值后,才可使⽤它。
int *a;*a=12; //只是声明了变量a,但从未对它初始化,因⽽我们没办法预测值12将存储在什么地⽅int *d=0; //这是可以的,0可以视作为零值int b=12;int *c=&b;另外C标准定义了NULL指针,它作为⼀个特殊的指针常量,表⽰不指向任何位置,因⽽对⼀个NULL指针进⾏解引⽤操作同样也是⾮法的。
因⽽在对指针进⾏解引⽤操作的所有情形前,如常规赋值、指针作为函数的参数,⾸先必须检查指针的合法性- ⾮NULL指针。
解引⽤NULL指针操作的后果因编译器⽽异,两个常见的后果分别是返回置0的值及终⽌程序。
总结下来,不论你的机器对解引⽤NULL指针这种⾏为作何反应,对所有的指针变量进⾏显式的初始化是种好做法。
如果知道指针被初始化为什么地址,就该把它初始化为该地址,否则初始化为NULL在所有指针解引⽤操作前都要对其进⾏合法性检查,判断是否为NULL指针,这是⼀种良好安全的编程风格1.1 指针运算基础在指针值上可以进⾏有限的算术运算和关系运算。
合法的运算具体包括以下⼏种: 指针与整数的加减(包括指针的⾃增和⾃减)、同类型指针间的⽐较、同类型的指针相减。
例如⼀个指针加上或减去⼀个整型值,⽐较两指针是否相等或不相等,但是这两种运算只有作⽤于同⼀个数组中才可以预测。
c语言中指针运用的研究
C语言中指针运用的研究钱惠恩(浙江工商大学统计与数学学院信息与计算科学专业,浙江杭州210018)明嘲糟针蔗c语寓的棱心。
被广泛使用。
它和擞组、字缔串、函数阍教播的传i蠢有看密不可分的联系,它的使用方式与方法缀常使初学者惑列迷惑。
D蝴】糯钟;播钟变量;基本用法;常见问题1指针的基本概念由计算机的工作原理我们知道,程序和数据都是以二进制代码的形式存放在内存中的,内存中的每个字节都有一个唯一地址。
如果要对一个数据进行一定操作,只有按照地址先找到这个数据,才能进行下一步的操作。
在C语言中,当我们定义了一个变量时,内存中就会为此变量分配一定的存储空间,这时,就有唯一地址与此变量相对应。
当我们引用变量名访问数据时,系统通过此变量名找到与之对应的内存地址,然后在对此地址下存放的数据进行操作。
对程序员来说,中间这个过程是透明的,就像是通过变量名直接访问数据一样。
这种通过变量名访问内存空间中数据的方式称为直接访问。
C语言提供了这种特殊类型的变量——指针类型。
这种类型的变量和其他变量一样,定义后在内存中占据一定的存储空间,用来存放数据,只不过这个数据将会被解释为地址。
所以,指针就是指地址。
对于一个指针变量,它的定义方式为:数据类型关键字,’变量名指针变量中存放的值为另一变量在内存中的地址,而指针变量的数据类型即其值所指地址中存放的数据的数据类型,*则标识此变量为一指针变量。
2C语言指针应用的优点指针是C语言的一个重要概念,也是C语言的一个重要组成部分,正确灵活地使用指针,能帮助我们解决许多实际问题。
主要有:1)指向单变量的指针变量作为函数参数,可以得到多个变化了的值,即函数调用可以而且只可以得到一个返回值,而运用指针变量参数,就可以得到多个变化了的值。
2)指向数组元素的指针变量处理数组,可以大大提高执行效率,所以指针使用熟练时,尽量使用指针处理数组。
3)指向字符串的指针变量构成指针数组处理多个字符串时可以节省内存空间,还可以提高程序的执行效率。
C语言中指针的应用
内存 为 其 分 配 2 个字 节 的 存储 单元 , 变量 C 浮 点型 ,内存为 其 分 配 4 是 个字 节的存 储 单元 。 内存为 变量 分 配存储 空 间 的首 个字 节单 元 的地址 称 为该 变 量 的地 址 。如 a的地 址 是 10 , 0 0 b的地 址 是 10 ,C 0 1 的地 址 是 10 。 由此可 03 见, 地址 就 是用 来标 识 每 一个 存 储单 元 的 , 以方 便 用 户对 存 储 单元 中的数 据 能够 正确 访 问 , 高级 语 言 中就 形象 地 将地 址 称为 指 针 。 针 可 以用 来指 向 在 指 变量 、 组 及 其 元 素 、 构 体 、 指 针 ( 为 多级 指 针 ) ,但不 能 用指 针 数 结 和 称 等 来 指 向表 达 式 、 量和 寄 存 器变 量 等 , 为这 些 对 象 没 有地 址 的 概 念 。 常 因
在 E 中 , 针 p itr 类型 是 it , 指 向 的类 型是 it 它 被 初始 例 指 one 的 n* 它 n, 化 为 指 向整 形 变 量 a 。接 下来 的 语 句 中 , 针 p itr被 加 了 1 编 译器 是 指 one , 这 样 处 理 的 : 把指 针 p itr 它 one 的值 加 了 s e f it ( l 位 程 序 中 , i o ( )在 6 z n 是
( )指 针 变 量 的 引 用 2
/ 此 处 略去 为 整 型数 组 赋值 的 代码 。 / fr(- ;< 0; o i0i2 i ++) {
…
(p itr +; * one )十
p n e ++ ; Oห้องสมุดไป่ตู้ t r
} 这 个 例 子 将 整 型 数 组 中 各 个元 素 的值 加 1 由于 每 次 循环 都 将 指 针 。 p itr加 1 以 每次 循 环都 能 访 问 数组 的下 一个 元 素 。 on e 所 所 以 ,一 个 指针 p i tr l 上一 个 整数 n后 ,结果 是一 个新 的指 针 one od加 p it w ,o tme one me p i e w的类 型和p itrl的类 型相 同 ,0 tr w所 指 向的 n oneod pi e n 类型 和D it od ̄指向的 类型 也相 同 。onenw的值 将  ̄p itrl的值增 one l): r ) i p itre goneo d 加 了 n 乘 s f(oneod  ̄ mo p it l所指 向 的类型 ) r 个字节 。 就是 说 , one w所 指 p itme 向的 内存 区将 比 p itrl 指 向的 内存区 向高地 址方 向移 动了 n 乘 s ef oneod所 i o z
c++数组参数传递
c++数组参数传递C语言是一种面向过程的语言,它的基本数据类型较为直接,也使得它的程序逻辑更为直接明了。
在C语言中,参数的传递方式主要是通过值传递、指针传递和引用传递三种方式。
其中,数组作为C语言中的重要数据结构,其参数传递方式也是值得探讨的。
一、数组作为函数参数在C语言中,数组作为函数参数时,有两种传递方式:值传递和指针传递。
1. 值传递:将整个数组作为参数传递给函数,函数内部通过局部变量来接收该数组的值。
这种方式下,函数内部对数组的修改不会影响到原数组。
2. 指针传递:将数组的指针作为参数传递给函数,函数内部通过该指针来访问和修改数组。
这种方式下,函数内部对数组的修改会影响到原数组。
二、数组参数传递的优缺点1. 优点:通过指针传递数组参数,可以在函数内部直接操作数组,无需再通过拷贝的方式进行传递,可以减少内存占用和时间成本。
同时,这种方式也使得函数的参数更简洁,易于理解。
2. 缺点:值传递的方式可能会影响性能,因为将整个数组作为参数传递会给函数传递的时间和空间带来一定的负担。
而且,这种方式下如果对数组进行了修改,修改并不会影响到原数组,这可能会引起一些误解和误操作。
三、注意事项在C语言中,数组作为参数传递时,需要注意以下几点:1. 指针传递时,需要确保传递的指针指向的内存空间是有效的,否则可能会导致程序崩溃或数据错误。
2. 数组作为参数传递时,需要注意数组的大小和类型是否符合函数的声明要求,否则可能会导致编译错误或运行时错误。
3. 在函数内部对数组进行操作时,需要注意越界访问和非法修改等问题,避免导致数据错误或程序崩溃。
四、实际应用举例下面是一个使用指针传递数组参数的示例代码:```c#include <stdio.h>void printArray(int *arr, int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");}int main() {int arr1[] = {1, 2, 3, 4, 5};int size = sizeof(arr1) / sizeof(arr1[0]);printArray(arr1, size); // 使用指针传递数组参数return 0;}```在这个示例中,`printArray`函数使用指针传递了`arr`和`size`两个参数。
c语言指针数组传参的方法
(原创实用版3篇)编制人员:_______________审核人员:_______________审批人员:_______________编制单位:_______________编制时间:____年___月___日序言下面是本店铺为大家精心编写的3篇《c语言指针数组传参的方法》,供大家借鉴与参考。
下载后,可根据实际需要进行调整和使用,希望能够帮助到大家,谢射!(3篇)《c语言指针数组传参的方法》篇1在 C 语言中,指针数组可以作为一个参数传递给函数。
传递指针数组的方法与传递普通数组的方法类似,只需要在数组名前加上指针符号(*)。
例如,有一个函数需要传递一个指针数组作为参数,该指针数组包含 10 个int 类型的指针,可以声明该函数的参数为:```cvoid function(int *arr[10]) {// 函数体}```在调用该函数时,可以传递一个指针数组作为参数,例如:```cint *p = arr; // 定义一个指向指针数组的指针function(p); // 传递指针数组作为参数```在函数内部,可以通过指针数组访问数组元素,例如:```cvoid function(int *arr[10]) {int i;for (i = 0; i < 10; i++) {printf("%d ", arr[i]); // 输出指针数组中的元素}}```需要注意的是,指针数组传递给函数后,函数内部只能访问指针数组中的元素,不能修改指针数组本身的内容。
《c语言指针数组传参的方法》篇2在 C 语言中,指针数组可以作为一个参数传递给函数。
传递指针数组的方法与传递普通数组的方法类似,只需要将数组的首地址作为参数传递即可。
例如,以下代码中,指针数组 p 被传递给函数 exchange,交换指针数组中前两个元素的值:```c#include <stdio.h>void exchange(int *p[], int n) {int a = p[0][0];p[0][0] = p[1][0];p[1][0] = a;}int main() {int p[][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}}; exchange(p, 3);printf("The array after exchange is:");for (int i = 0; i < 3; i++) {for (int j = 0; j < 5; j++) {printf("%d ", p[i][j]);}printf("");}return 0;}```在上面的代码中,指针数组 p 是一个二维数组,包含 3 个一维数组,每个一维数组有 5 个整数。
C语言指针用法详解
C语言指针用法详解C语言指针用法详解指针可以说是集C语言精华之所在,一个C语言达人怎么可以不会指针呢。
下面店铺给大家介绍C语言指针用法,欢迎阅读!C语言指针用法详解(1)关于指针与数组的存储a、指针和数组在内存中的存储形式数组p[N]创建时,对应着内存中一个数组空间的分配,其地址和容量在数组生命周期内一般不可改变。
数组名p本身是一个常量,即分配数组空间的地址值,这个值在编译时会替换成一个常数,在运行时没有任何内存空间来存储这个值,它和数组长度一起存在于代码中(应该是符号表中),在链接时已经制定好了;而指针*p创建时,对应内存中这个指针变量的空间分配,至于这个空间内填什么值即这个指针变量的值是多少,要看它在程序中被如何初始化,这也决定了指针指向哪一块内存地址。
b、指针和数组的赋值与初始化根据上文,一般情况下,数组的地址不能修改,内容可以修改;而指针的内容可以修改,指针指向的内容也可以修改,但这之前要为指针初始化。
如:int p[5];p=p+1; 是不允许的而p[0]=1; 是可以的;//int *p;p=p+1; 是允许的p[0]=1; 是不允许的,因为指针没有初始化;//int i;int *p=&i;p[0]=1; 是允许的;对于字符指针还有比较特殊的情况。
如:char * p="abc";p[0]='d'; 是不允许的为什么初始化了的字符指针不能改变其指向的内容呢?这是因为p 指向的是“常量”字符串,字符串"abc"实际是存储在程序的静态存储区的,因此内容不能改变。
这里常量字符串的地址确定在先,将指针指向其在后。
而char p[]="abc";p[0]='d'; 是允许的这是因为,这个初始化实际上是把常量直接赋值给数组,即写到为数组分配的内存空间。
这里数组内存分配在先,赋值在后。
(2)关于一些表达式的含义char *p, **p, ***p;char p[],p[][],p[][][];char *p[],*p[][],**p[],**p[][],*(*p)[],(**p)[],(**p)[][];能清晰地知道以上表达式的含义吗?(知道的去死!)第一组:char *p, **p, ***p;分别为char指针;char*指针,即指向char*类型数据地址的指针;char**指针,即指向char**类型数据的指针;他们都是占4字节空间的指针。
指针的常用用法
指针的常用用法
指针是C语言中的一种特殊数据类型,它存储了一个变量的内存地址。
指针的常用用法如下:
1. 用指针访问变量:可以通过指针访问变量的值。
例如,如果有一个整型变量x和一个指向该变量的指针p,可以使用*p的方式来访问x的值。
2. 传递指针给函数:可以将指针作为参数传递给函数,从而在函数中可以修改指针所指向的变量。
这样可以避免在函数内部复制变量的开销。
可以使用指针作为函数参数来实现传递引用的效果。
3. 动态内存分配:可以使用指针来分配和释放内存。
通过动态内存分配,可以在运行时根据需要动态地分配内存,从而更灵活地管理内存空间。
4. 数组与指针的关系:数组名实际上是指向数组第一个元素的指针。
可以通过指针来访问和操作数组的元素。
5. 字符串处理:字符串在C语言中是以字符数组的形式存在的。
可以使用指针来处理字符串,例如按字符遍历字符串、比较字符串等。
6. 指针和结构体:可以使用指针来访问和操作结构体变量的成员。
7. 指针的运算:指针可以进行加减运算,可以用来实现遍历数组、访问连续内存空间等功能。
8. 空指针:空指针是指不指向任何有效的内存地址的指针,可以使用NULL宏定义来表示。
空指针常用于初始化指针、判断指针是否为空等操作。
这些是指针的常用用法,通过合理运用指针,可以提高程序的效率、节省内存资源,并且可以更灵活地处理数据。
但是需要注意指针的使用时要小心,避免出现空指针引用、野指针等问题,以确保程序的正确性和安全性。
c++值传递,指针传递,引用传递以及指针与引用的区别
c++值传递,指针传递,引⽤传递以及指针与引⽤的区别值传递:形参是实参的拷贝,改变形参的值并不会影响外部实参的值。
从被调⽤函数的⾓度来说,值传递是单向的(实参->形参),参数的值只能传⼊,不能传出。
当函数内部需要修改参数,并且不希望这个改变影响调⽤者时,采⽤值传递。
指针传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本⾝进⾏的操作引⽤传递:形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引⽤传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。
被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
举例:#include<iostream>using namespace std;//值传递void change1(int n){cout<<"值传递--函数操作地址"<<&n<<endl; //显⽰的是拷贝的地址⽽不是源地址n++;}//引⽤传递void change2(int & n){cout<<"引⽤传递--函数操作地址"<<&n<<endl;n++;}//指针传递void change3(int *n){cout<<"指针传递--函数操作地址 "<<n<<endl;*n=*n+1;}int main(){int n=10;cout<<"实参的地址"<<&n<<endl;change1(n);cout<<"after change1() n="<<n<<endl;change2(n);cout<<"after change2() n="<<n<<endl;change3(&n);cout<<"after change3() n="<<n<<endl;return true;}运⾏结果:可以看出,实参的地址为0x28ff2c采⽤值传递的时候,函数操作的地址是0x28ff10并不是实参本⾝,所以对它进⾏操作并不能改变实参的值再看引⽤传递,操作地址就是实参地址,只是相当于实参的⼀个别名,对它的操作就是对实参的操作接下来是指针传递,也可发现操作地址是实参地址那么,引⽤传递和指针传递有什么区别吗?引⽤的规则:(1)引⽤被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
C语言指针知识点总结
C语⾔指针知识点总结1.指针的使⽤和本质分析(1)初学指针使⽤注意事项1)指针⼀定要初始化,否则容易产⽣野指针(后⾯会详细说明);2)指针只保存同类型变量的地址,不同类型指针也不要相互赋值;3)只有当两个指针指向同⼀个数组中的元素时,才能进⾏指针间的运算和⽐较操作;4)指针只能进⾏减法运算,结果为同⼀个数组中所指元素的下表差值。
(2)指针的本质分析①指针是变量,指针*的意义:1)在声明时,*号表⽰所声明的变量为指针。
例如:int n = 1; int* p = &n;这⾥,变量p保存着n的地址,即p<—>&n,*p<—>n2)在使⽤时,*号表⽰取指针所指向变量的地址值。
例如:int m = *p;②如果⼀个函数需要改变实参的值,则需要使⽤指针作为函数参数(传址调⽤),如果函数的参数数据类型很复杂,可使⽤指针代替。
最常见的就是交换变量函数void swap(int* a, int* b)③指针运算符*和操作运算符的优先级相同例如:int m = *p++;等价于:int m= *p; p++;2.指针和数组(1)指针、数组、数组名如果存在⼀个数组 int m[3] = {1,2,3};定义指针变量p,int *p = m(这⾥m的类型为int*,&a[0]==>int*)这⾥,其中,&m为数组的地址,m为数组0元素的地址,两者相等,但意义不同,例如:m+1 = (unsigned int)m + sizeof(*m)&m+1= (unsigned int)(&m) + sizeof(*&m)= (unsigned int)(&m) + sizeof(m)m+1表⽰数组的第1号元素,&m+1指向数组a的下⼀个地址,即数组元素“3”之后的地址。
等价操作:m[i]←→*(m+i)←→*(i+m)←→i[m]←→*(p+i)←→p[i]实例测试如下:1 #include<stdio.h>23int main()4 {5int m[3] = { 1,2,3 };6int *p = m;78 printf(" &m = %p\n", &m);9 printf(" m = %p\n", m);10 printf("\n");1112 printf(" m+1 = %p\n", m + 1);13 printf(" &m[2] = %p\n", &m[2]);14 printf(" &m+1 = %p\n", &m + 1);15 printf("\n");1617 printf(" m[1] = %d\n", m[1]);18 printf(" *(m+1) = %d\n", *(m + 1));19 printf(" *(1+m) = %d\n", *(1 + m));20 printf(" 1[m] = %d\n", 1[m]);21 printf(" *(p+1) = %d\n", *(p + 1));22 printf(" p[1] = %d\n", p[1]);2324return0;25 }输出结果为:(2)数组名注意事项1)数组名跟数组长度⽆关;2)数组名可以看作⼀个常量指针;所以表达式中数组名只能作为右值使⽤;3)在以下情况数组名不能看作常量指针:- 数组名作为sizeof操作符的参数- 数组名作为&运算符的参数(3)指针和⼆维数组⼀维数组的指针类型是 Type*,⼆维数组的类型的指针类型是Type*[n](4)数组指针和指针数组①数组指针1)数组指针是⼀个指针,⽤于指向⼀个对应类型的数组;2)数组指针的定义⽅式如下所⽰:int (*p)[3] = &m;②指针数组1)指针数组是⼀个数组,该数组⾥每⼀个元素为⼀个指针;2)指针数组的定义⽅式如下所⽰:int* p[5];3.指针和函数(1)函数指针函数的本质是⼀段内存中的代码,函数的类型有返回类型和参数列表,函数名就是函数代码的起始地址(函数⼊⼝地址),通过函数名调⽤函数,本质为指定具体地址的跳转执⾏,因此,可定义指针,保存函数⼊⼝地址,如下所⽰:int funcname(int a, int b);int(*p)(int a, int b) = funcname;上式中,函数指针p只能指向类型为int(int,int)的函数(2)函数指针参数对于函数int funcname(int a, int b);普通函数调⽤ int funcname(int, int),只能调⽤函数int func(int, int)函数指针调⽤ intname(*func)(int,int),可以调⽤任意int(int,int)类型的函数,从⽽利⽤相同代码实现不同功能,实例测试如下,假设有两个相同类型的函数func1和func2:1int func1(int a, int b, int c)2 {3return a + b + c;4 }56int func2(int a, int b, int c)7 {8return a - b - c;9 }普通函数调⽤和函数指针调⽤⽅式及结果如下所⽰1 printf("普通函数调⽤\n");2 printf("func1 = %d\n", func1(100, 10, 1));3 printf("func2 = %d\n", func2(100, 10, 1));4 printf("\n");56 printf("函数指针调⽤\n");7int(*p)(int, int, int) = NULL;8 p = func1;9 printf("p = %d\n", p(100, 10, 1));10 p = func2;11 printf("p = %d\n", p(100, 10, 1));12 printf("\n");需要注意的是,数组作为函数参数的时候,会变为函数指针参数,即:int funcname( int m[] )<——>int funcname ( int* m );调⽤函数时,传递的是数组名,即funcname(m);(3)回调函数利⽤函数指针,可以实现⼀种特殊的调⽤机制——回调函数。
c语言指针面试常见问题及解析
c语言指针面试常见问题及解析c语言指针面试常见问题及解析分析下面的程序,指出程序中的错误:本题解析没有正确为str分配内存空间,将会发生异常。
问题出在将一个字符串复制进一个字符变量指针所指地址。
虽然编译的时候没有报错,但是在运行过程中,因为越界访问了未被分配的内存,而导致段错误。
相关知识点在处理与指针相关的问题时,首先需要搞明白的就是内存,因为指针操作的就是内存。
第一个,就是内存的分区。
这也是经常会被考察的一个考点。
写出内存分为几大区域对于这个问题,有几种不同的说法,有的说内存分为五大分区,有的说分为四大分区,我们先来看五大分区的说法:认为内存分为五大分区的人,通常会这样划分:1、BSS段( bss segment )通常是指用来存放程序中未初始化的全局变量和静态变量(这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。
既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。
BSS是英文Block Started by Symbol的简称。
BSS段属于静态内存分配。
BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小。
以便内存区能在运行时分配并被有效地清零。
BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。
2、数据段(data segment)通常是指用来存放程序中已经初始化的全局变量和静态变量的一块内存区域。
数据段属于静态内存分配,可以分为只读数据段和读写数据段。
字符串常量等,但一般都是放在只读数据段中。
3、代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。
第十章 指针指针是C语言中广泛使用的一种数据类型. 运用指针.
int *p; p=1000;
被赋值的指针变量前不能再加“*”说明符,如写为*p=&a 也是 错误的。
3、指针变量的引用
欲穷千里,更上层楼
两个指针运算符: (1)取地址运算符:& (2)取内容运算符:*
例如: &a为变量a的地址,*p为指针变量p所指向的变 量
#include ”stdio.h”
#include “conio.h”
表示对数组元素a[2]赋以值1
C规定p+1指向下一个元素(实际含义为p+1*d,d为一个数组元素 所占字节数)
如果p的初值为&a[0],则:
p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i元素
*(p+i) 或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。
p a数组
a[0]
p+1,a+1
a[1]
p+i,a+i
*(p+i)
a[i]
p+9,a+9
a[9]
欲穷千里,更上层楼
p,a,&a[0]均指向同一单 元,它们是数组a的首地 址,也是第0 元素a[0]的 地址。
p+1,a+1,&a[1]均指向 第1元素a[1]。类推可知 p+i,a+i,&a[i]指向第i元素 a[i]。
应该说明的p是变量, 而a,&a[i]都是常量。在 编程时应予以注意。
2)在讲述一维数组时候我们曾经提到: 因为: a[i]和*(a+i) 等价! 所以: a[i]+j= =*(a+i)+j= =&a[i][j]
c语言中的指针是什么
c语言中的指针是什么很多学习C语言的新手来说,指针无疑是一个难点。
但是,我觉得指针也是C语言特别重要的一个特性。
那么下面一起来看看店铺为大家精心推荐的c语言中的指针是什么,希望能够对您有所帮助。
为什么说指针是 C 语言的精髓?“指”是什么意思?其实完全可以理解为指示的意思。
比如,有一个物体,我们称之为A。
正是这个物体,有了这么个称谓,我们才能够进行脱离这个物体的实体而进行一系列的交流。
将一个物体的指示,是对这个物体的抽象。
有了这种抽象能力,才有所谓的智慧和文明。
所以这就是“指示”这种抽象方法的威力。
退化到C语言的指针,指针是一段数据/指令(在冯诺易曼体系中,二者是相通,在同一空间中的)的指示。
这是指示,也就是这段数据/指令的起始位置。
但是数据/代码是需要一个解释的方法的。
比如0x0001,可以作为一个整数,也可以作为作为一串指令,也可以作为一串字符,总之怎样解释都可以。
而C语言,在编译阶段,确定了这段数据/指令的“解释方法”。
例如,整型指针,表示的就是可以从这个指针p指向的位置开始解释,解释为一个整数。
一个函数指针,表示的就是可以从这个指针p指向的位置开始解释,解释为一段指令,对应的输入和输出以及返回值按照函数指针的类型,符合相应的要求。
综上,C语言的精髓是指针,但指针不仅仅是C语言的精髓,它是抽象的精髓。
各个语言中都有类似的东西,例如函数,例如引用。
(引用和指针的区别,我的理解,不可以进行+/-偏移操作的指针,就是引用。
随意偏移,很容易使得目标位置不符合其相应的意义,从而造成解释失败,进而崩溃。
而增加了偏移功能的指针,好处是方便表述一堆具有相同类型的数据/指令,数组之类的就是这样的实例。
) 同样的void类型的指针,也是C语言的特色。
void型的指针,就是去掉了指定类型的指针,从而使得可以以任意解释方式,解释指针,这就带来了如上的潜在问题。
但是也可以说,这个C语言的特有威力(我一般都把C语言的威力理解为这个)。
c语言面试题目及答案
c语言面试题目及答案C语言作为一门广泛应用于编程领域的计算机语言,在面试中经常作为一个重要的考察内容。
本文将为您提供一些常见的C语言面试题目及其答案,帮助您更好地准备面试。
1. 请解释一下C语言中的自动变量和静态变量的区别。
自动变量是在函数内部定义的变量,它的作用域仅限于函数内部。
当函数调用结束时,自动变量的内存空间将被释放。
静态变量则是在函数外部定义的变量,在整个程序的执行过程中都会存在。
静态变量的作用域仅限于定义它的文件内部,其他文件无法访问。
静态变量会在程序运行时自动初始化,并且只会被初始化一次。
2. 请说明指针和数组的关系。
指针和数组在C语言中有着密切的关系。
数组实际上是由若干个连续的内存单元组成,而指针则可以指向这些内存单元的首地址。
通过使用指针,我们可以通过指针操作数组元素,实现对数组的遍历、修改等操作。
同时,数组名本身就是一个指向数组首元素地址的指针,可以通过指针运算和指针的偏移实现对数组元素的访问。
3. C语言中的宏定义有什么作用?宏定义是一种预处理指令,用于在代码中定义一个标识符作为一个常量或一个代码片段的别名。
宏定义可以提高代码的可读性和维护性,可以用宏定义来定义一些经常使用的常量、函数和代码段。
通过宏定义,我们可以将一些常用的、重复的代码片段定义成一个宏,并使用该宏来简化代码。
4. 在C语言中,什么是指针的指针?指针的指针是指一个指针变量存储的是另一个指针变量的地址。
简单来说,它是指向指针的指针。
通过使用指针的指针,我们可以实现多级指针的操作,能够更灵活地处理指针所指向的内存地址。
在某些情况下,使用指针的指针可以更方便地操作和传递指针变量。
5. 请解释一下C语言中的动态内存分配。
动态内存分配是指在程序运行时根据需要从堆中分配内存,并在使用完毕后释放内存。
在C语言中,我们可以使用malloc()函数来在堆中分配内存,并使用free()函数来释放已分配的内存。
通过动态内存分配,我们可以根据实际需要动态申请和释放内存,提高内存的利用效率。
c语言结构体指针拷贝
c语言结构体指针拷贝C语言结构体指针拷贝在C语言中,结构体是一种可以存储不同数据类型的组合类型。
结构体的指针则是指向结构体变量的指针,可以通过指针来访问和操作结构体变量。
在某些情况下,我们需要对结构体指针进行拷贝操作,可以使用浅拷贝和深拷贝的方式来完成。
本文将逐步回答关于C语言结构体指针拷贝的相关问题,并探讨拷贝操作的原理和注意事项。
一、为什么要进行结构体指针拷贝?在C语言中,我们通常使用结构体指针来传递和操作结构体变量,因为结构体变量作为函数参数传递时会进行值拷贝,这对于大型的结构体来说会带来一定的性能开销。
而结构体指针的拷贝只是指针的复制,不涉及到结构体的数据复制,所以比值拷贝更加高效。
另外,结构体指针拷贝还有助于在函数间共享和修改数据。
通过拷贝指针,不同的函数可以引用同一个结构体对象,从而实现在不同函数之间交换或修改结构体对象的数据。
二、浅拷贝和深拷贝的区别是什么?在进行结构体指针拷贝时,有两种常见的拷贝方式,即浅拷贝和深拷贝。
浅拷贝是指仅拷贝指针本身,而不拷贝指针所指向的内存区域。
这意味着拷贝后的指针和原指针指向同一块内存区域,修改任意一个指针都会影响到另一个指针。
浅拷贝一般使用赋值运算符("=")来完成,例如:cstruct Person {char name[20];int age;};struct Person* p1 = (struct Person*)malloc(sizeof(struct Person)); struct Person* p2 = NULL;p1->age = 20;p2 = p1; 浅拷贝p2->age = 30;printf("d\n", p1->age); 输出结果为30深拷贝是指在拷贝指针本身的同时,还要拷贝指针所指向的内存区域。
这样,拷贝前后的指针将各自指向一块独立的内存区域,互不影响。
深拷贝一般需要手动分配内存,并通过memcpy等函数进行内存拷贝,例如:cstruct Person* p1 = (struct Person*)malloc(sizeof(struct Person)); struct Person* p2 = NULL;p1->age = 20;p2 = (struct Person*)malloc(sizeof(struct Person)); 创建新的结构体对象memcpy(p2, p1, sizeof(struct Person)); 深拷贝p2->age = 30;printf("d\n", p1->age); 输出结果为20三、如何进行结构体指针的浅拷贝?结构体指针的浅拷贝非常简单,只需要使用赋值运算符("=")将一个指针赋值给另一个指针即可。
c语言 参数不同的函数指针
c语言参数不同的函数指针函数指针在C语言中是一种特殊的指针变量,它指向的函数不需要任何参数或者不需要返回值。
在C语言中,可以使用函数指针来实现回调函数,传递不同的参数类型给不同的函数。
在本文中,我们将讨论如何在C语言中使用参数不同的函数指针。
一、基本概念首先,让我们了解什么是函数指针。
在C语言中,一个函数指针就是一个指向函数的指针变量。
该变量存储了函数的地址,并且可以像函数一样被调用。
当需要将不同类型的函数传递给同一函数时,就可以使用函数指针。
二、声明函数指针要声明一个函数指针,需要指定要指向的函数的返回类型和参数列表。
可以使用以下语法来声明函数指针:类型名* 变量名(参数列表) {// 函数体}例如,假设有一个函数定义如下:int add(int a, int b) {return a + b;}要声明一个指向该函数的指针,可以使用以下代码:int (*add_func_ptr)(int, int);现在,add_func_ptr就是一个函数指针,可以用来调用add函数。
使用参数不同的函数指针,可以根据需要传递不同的参数给不同的函数。
例如,假设有两个不同的函数需要传递两个整数参数给它们:void func1(int a, int b) {// 执行操作}void func2(double a, double b) {// 执行操作}要使用参数不同的函数指针来调用这些函数,可以使用以下代码:// 声明一个函数指针数组,包含两个指向不同函数的指针void (*func_ptrs[2])(int, int);// 将指向func1函数的指针赋值给func_ptrs数组的第一个元素func_ptrs[0] = func1;// 将指向func2函数的指针赋值给func_ptrs数组的第二个元素func_ptrs[1] = func2;// 使用参数不同的函数指针调用这些函数int result = func_ptrs[0](1, 2); // 使用func1函数并传递整数参数1和2double result_double = func_ptrs[1](3.14, 5.76); // 使用func2函数并传递双精度浮点数参数3.14和5.76注意,在实际使用时,可能需要检查函数的返回值以确定其返回类型。
c语言函数传输传递的三种方式(值、指针、引用)
c语⾔函数传输传递的三种⽅式(值、指针、引⽤)本⽂摘⾃《彻底搞定c指针》⼀、三道考题开讲之前,我先请你做三道题⽬。
(嘿嘿,得先把你的头脑搞昏才⾏……唉呀,谁扔我鸡蛋?)考题⼀,程序代码如下:void Exchg1(int x, int y){int tmp;tmp = x;x = y;y = tmp;printf("x = %d, y = %d\n", x, y);}main(){int a = 4,b = 6;Exchg1(a, b);printf("a = %d, b = %d\n", a, b);return(0);}输出的结果为: 20x = ____, y=____.a = ____, b=____.问下划线的部分应是什么,请完成。
考题⼆,程序代码如下:void Exchg2(int *px, int *py){int tmp = *px;*px = *py;*py = tmp;printf("*px = %d, *py = %d.\n", *px, *py);}main(){int a = 4;int b = 6;Exchg2(&a, &b);printf("a = %d, b = %d.\n", a, b);return(0);}输出的结果为为:*px=____, *py=____.a=____, b=____.问下划线的部分应是什么,请完成。
考题三,程序代码如下:void Exchg3(int &x, int &y)21{int tmp = x;x = y;y = tmp;printf("x = %d,y = %d\n", x, y);}main(){int a = 4;int b = 6;Exchg3(a, b);printf("a = %d, b = %d\n", a, b);return(0);}输出的结果为:x=____, y=____.a=____, b=____.问下划线的部分应是什么,请完成。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C语言中一个关于指针传递的问题李云UTStarcom通讯有限公司 E-Box Team2005-06-22摘要指针在C语言中扮演着极为重要的角色,它的存在为C语言提供了极大的灵活性,当然,不少问题也是由指针所引起的(双刃剑)。
本文通过分析一个由指针传递所引起的错误,从而使得我们更加重视指针在编程中的传递问题。
关键词C语言指针传递缩略语SignificantByte 最低有效字节LeastLSBMCI Management & Control Interface 管理控制接口Byte 最高有效字节MSB MostSignificant1 问题的提出指针因为灵活使得我们在编程时有意识的利用这一特性,从而使得我们的设计也更加的灵活,如函数指针等等。
在很多情况下,我们需要从被调用函数返回结果。
这可以通过两种方法来实现,一是通过函数的返回值,二是通过将指针作为参数传递给被调用函数。
图 1.1就是一个例子。
00001:S32 mci_module_id_from_name(S8* name, U16* module_id)00002:{00003:mci_module_t *module;00004:U16 index = 0;00005:00006:if(name == NULL || module_id == NULL)00007:return ERR_MCI_INV_PRARAM;00008:00009:for(;index <= g_mci_last_module_id; index ++)00010:{00011:module = g_mci_module_array[index];00012:00013:if(module == NULL)00014:continue;00015:00016:if(strcmp(module->name, name) == 0)00017:{00018:*module_id = index;00019:return 0;00020:}00021:}00022:00023:return ERR_MCI_MOD_NOT_EXIST;00024:}图 1.1 采用指针传递获取返回结果的示例函数在图 1.1中需要关心的是第18行,这一行将找到的MCI模块的ID通过指针传递的方法,将其返回给调用者。
图 1.2是使用图 1.1的mci_module_id_from_name函数的一个例子程序。
00026:int foo(char* name)00027:{00028:U32 module_id = 0;00029:00030:if (mci_module_id_from_name(name, &module_id) < 0)00031:return FAILURE;00032:00033:...00034:00035:return SUCCESS;00036:}图 1.2 使用mci_module_id_from_name函数的一个例子foo函数中对于mci_module_id_from_name函数的调用永远能得到正确的结果吗?答应案是:否。
如果在x86处理器上运行这一程序,则总是能得到正确的结果,但在我们熟悉的PowerPC处理器上运行这一程序则总是很难得到正确的结果(只有当module_id_from_name函数中返回的module_id恰好为0时结果才正确)。
这是为什么呢?产生这一问题的关键是:mci_module_id_from_name函数需要的是一个U16(我们可以理解为32位处理器上的unsigned short int)的指针,但foo函数在调用mci_module_id_from_name时,所给的指针是U32(我们可以理解为32位处理器上的unsigned int)。
这一程序如果在Visual C++中进行编译,则会出现编译错误(指出指针类型不匹配),为了能在Visual C++中编译通过,则需要做一个将U32*强制转换成U16*的转换(如(U16*)&module_id)。
但在我们所使用的VxWorks编译环境中,是能正常编译并且不会出现任何的告警信息的(见注)。
注:这一问题的出现是在VxWorks中采用-ansi编译选项,且在foo函数所在的文件中没有声名mci_module_id_from_name的原型的情况下出现的。
为了让编译器能检查出这一函数指错不匹配的问题可以采用-std=c9x等编译选项进行编译。
当采用-std=c9x编译选项进行编译时,如果在函数调用处,没有找到被调用函数的原型声名,则会报错。
相同的程序,为什么在x86和PowerPC处理器上却会产生截然不同的结果呢?对于这一问题,两种处理器的字节顺序问题是其根源,即x86采用的little-endian,而PowerPC 采用的big-endian。
2 little-endian和big-endianlittle-endian采用低位字节放在低地址内存,而高位字节放在高地址内存的方法。
反之,big-endian是采用高位字节放在低地址内存,低位字节放在高地址内存的方法。
以图 1.2的foo函数中的module_id变量为例(由于是局部变量,所在这一变量位于堆栈内存中),由于module_id被定义为U32,因此,它将占用4个字节的内存,假设module_id位于0x10000地址开始的内存中,则在little-endian的CPU上,其字节在内存中的存放顺序如图 2.1所示,而在big-endian的CPU上其字节在内存中的存放顺序如图 2.2所示。
U32 module_id = 00x100000x100010x100020x10003图 2.1 module_id在little-endian下的字节存放顺序U32 module_id = 00x100000x100010x100020x10003图 2.2 module_id在big-endian下的字节存放顺序思考TCP/IP协议采用的是big-endian模式,因此,在进行网络套接字(socket)编程时,我们需要用到htonl、ntohl等函数,为什么?请仔细分析在不同endian模式的两台主机之间进行通讯时,所需发送的数据在发送主机上的字序存储方式、数据在网络上的传输顺序、以及数据在接收主机上的字序存储方式。
3 问题的分析在知道little-endian和big-endian以后,对于前面所述问题的分析就比较容易了。
首先,让我们看一看在little-endian模式下图 1.2的程序为什么总能得到正确的结果,在分析问题之前,我们仍然以本文第2节的假设为前提,即foo函数中的局部变量module_id 是存放在内存地址0x10000开始的地方(占4个字节)。
由于foo是将module_id的地址传给mci_module_id_from_name函数的,因此,0x10000被传递给mci_module_id_from_name函数。
现在假设在mci_module_id_from_name中需要对module_id赋值为258(即*module_id = 258)。
这一过程在little-endian模式下如图 3.1所示。
U32 module_id = 0* module_id =0x100000x100000x100010x100020x10003foo函数作用范围mci_module_id_from_name函数作用范围U32 module_id = 0* module_id =0x100000x100000x100010x100020x10003斌值操作图 3.1 module_id在little-endian下通过指针传递的操作过程在mci_module_id_from_name函数中,由于module_id被定义为U16的指针,因此,从0x10000开始的2个字节被用来存放module_id的值。
由于little-endian的字序特征,在mci_module_id_from_name中对U16类型module_id的修改,总是能正确的返回到foo 函数中的U32的module_id。
同样的情况在big-endian下又会是怎样呢?如图 3.2所示。
U32 module_id = 0* module_id =0x100000x100000x100010x100020x10003foo函数作用范围mci_module_id_from_name函数作用范围U32 module_id = 0* module_id =0x100000x100000x100010x100020x10003斌值操作图 3.2 module_id在big-endian下通过指针传递的操作过程从图 3.2不难看出,在mci_module_id_from_name函数中我们对module_id赋值为258,但返回到foo函数以后这一值变成了16908288,这显然不是我们所希望的。
产生这一问题的根源还是因为big-endian的字序特征所致。
从以上分析来看,是不是在little-endian下foo函数在调用mci_module_id_from_name 时指针传递的类型不匹配就一定不会产生问题呢?答案是不一定,在foo这一函数中我有意识的在调用mci_module_id_from_name函数前将module_id初始化为0。
从图 3.1可以看出在mci_module_id_from_name函数中只对低两个字节进行赋值操作,而对高两个字节根本不去做任何的改变。
因此,如果module_id的高两字节的值如不为0,则调用mci_module_id_from_name函数同样不能得到正确的结果。
4 总结指针传递应当严格按照所需的指针类型进行传递,否则有可能造成程序无法正常运行。
在进行程序设计时,应尽可能的保证特定的对象类型的一致性,从而可以有效的避免指针类型不匹配这一问题。