C语言中一个关于指针传递的问题
- 格式:pdf
- 大小:140.02 KB
- 文档页数:5
c语言函数指针传递C语言函数指针传递函数指针是C语言中非常重要的概念之一,它可以使得程序更加灵活和可扩展。
函数指针的传递是指将一个函数的指针作为参数传递给另一个函数,使得后者可以使用前者所指向的函数。
在C语言中,函数指针的类型与被指向的函数的类型是一致的。
可以使用typedef关键字来定义函数指针类型,以提高代码的可读性。
例如,可以使用以下方式定义一个函数指针类型:typedef void (*FuncPtr)(int);其中,FuncPtr是函数指针类型的名称,void是被指向的函数的返回类型,而(int)是被指向的函数的参数列表。
函数指针的传递可以使得程序更加灵活,可以根据实际需要动态地改变要执行的函数。
例如,可以定义一个函数,该函数接受一个函数指针作为参数,并在函数内部调用该指针所指向的函数。
这样,在调用该函数时,可以传入不同的函数指针,从而实现不同的功能。
这种方式常常被用于回调函数的实现。
回调函数是指当某个事件发生时,系统或程序会调用预先注册的函数,以完成特定的任务。
通过函数指针的传递,可以将回调函数的实现与事件的触发进行解耦,使得系统或程序的设计更加灵活和可扩展。
除了回调函数,函数指针的传递还可以用于其他一些场景。
例如,可以将一个函数指针作为参数传递给另一个函数,以实现函数的动态调用。
在这种情况下,函数指针可以作为一个函数库的接口,从而使得函数库的用户可以根据实际需要选择要调用的函数。
函数指针的传递还可以用于实现函数的多态性。
多态性是指同一操作作用于不同的对象上时,可以有不同的解释和执行方式。
通过将不同的函数指针传递给同一个函数,可以使得该函数在不同的情况下具备不同的行为。
需要注意的是,在使用函数指针传递时,需要确保传递的函数指针所指向的函数与接收参数的函数的参数列表和返回类型是一致的。
否则,在函数调用时可能会出现编译错误或运行时错误。
函数指针的传递是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
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语言数组参数与指针参数我们都知道参数分为形参和实参。
形参是指声明或定义函数时的参数,而实参是在调用函数时主调函数传递过来的实际值。
一、一维数组参数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语⾔(⼀)数组、函数与指针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语言中指针运用的研究钱惠恩(浙江工商大学统计与数学学院信息与计算科学专业,浙江杭州210018)明嘲糟针蔗c语寓的棱心。
被广泛使用。
它和擞组、字缔串、函数阍教播的传i蠢有看密不可分的联系,它的使用方式与方法缀常使初学者惑列迷惑。
D蝴】糯钟;播钟变量;基本用法;常见问题1指针的基本概念由计算机的工作原理我们知道,程序和数据都是以二进制代码的形式存放在内存中的,内存中的每个字节都有一个唯一地址。
如果要对一个数据进行一定操作,只有按照地址先找到这个数据,才能进行下一步的操作。
在C语言中,当我们定义了一个变量时,内存中就会为此变量分配一定的存储空间,这时,就有唯一地址与此变量相对应。
当我们引用变量名访问数据时,系统通过此变量名找到与之对应的内存地址,然后在对此地址下存放的数据进行操作。
对程序员来说,中间这个过程是透明的,就像是通过变量名直接访问数据一样。
这种通过变量名访问内存空间中数据的方式称为直接访问。
C语言提供了这种特殊类型的变量——指针类型。
这种类型的变量和其他变量一样,定义后在内存中占据一定的存储空间,用来存放数据,只不过这个数据将会被解释为地址。
所以,指针就是指地址。
对于一个指针变量,它的定义方式为:数据类型关键字,’变量名指针变量中存放的值为另一变量在内存中的地址,而指针变量的数据类型即其值所指地址中存放的数据的数据类型,*则标识此变量为一指针变量。
2C语言指针应用的优点指针是C语言的一个重要概念,也是C语言的一个重要组成部分,正确灵活地使用指针,能帮助我们解决许多实际问题。
主要有:1)指向单变量的指针变量作为函数参数,可以得到多个变化了的值,即函数调用可以而且只可以得到一个返回值,而运用指针变量参数,就可以得到多个变化了的值。
2)指向数组元素的指针变量处理数组,可以大大提高执行效率,所以指针使用熟练时,尽量使用指针处理数组。
3)指向字符串的指针变量构成指针数组处理多个字符串时可以节省内存空间,还可以提高程序的执行效率。
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`两个参数。
(原创实用版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语言指针用法详解(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语言中一个关于指针传递的问题李云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 总结指针传递应当严格按照所需的指针类型进行传递,否则有可能造成程序无法正常运行。
在进行程序设计时,应尽可能的保证特定的对象类型的一致性,从而可以有效的避免指针类型不匹配这一问题。