A. C语言的题目 在线等 谢谢~
这个表达式的求值顺序为从右到左:
首先计算a/a的值为1,然后计算下一部分赋值语句,也就是a+=1,a的值变成5+1也就是6;下一步是a*=6,a自乘一次变成36;接下来计算a-=36,于是a=a-36,结果为0。
测试如图,环境为WindowsXPSP3+MinGW(gcc4.3),以及ubuntu9.04(Linux2.6)gcc4.3
实际上如图所示,gcc在-Wall开关下会提示a的这种赋值可能是未定义的行为,因而可能不同的编译器实现会有不同的结果。也就是说,尽管ANSIC99标准中规定了表达式求值的顺序是从右到左,但是并没有规定在一个表达式中一个变量出现多次时,这个变量的值在计算过程中是不是会变。
上面给出的gcc的实现方式是对a随时求值并赋值,然而另一个编译器可能会这样做:(首先咱们通俗的说一下^_^)
我们记住了a的值是5,然后开始从右到左计算:a/a值是1;接下来计算a+=1,根据我们的记忆,a的值是5,于是这部分的值就是6;同样,计算a*=6时,根据我们的记忆,a的值是5,所以结果变成30;然后a-=30,便计算出5-30也就是-25的结果。
这一种理解方式可能跟我们看到的表达式不同,毕竟我们见到的表达式中是随时赋值,而编译器理解成a-=(a*(a+(a/a)))并不是我们想说的计算,——正因为这个原因,大部分编译器实现时都采用的是第一种理解方式,也就是说,尽管C99中没有明确规定,但是大家已经比较统一的采用了第一种也是比较自然的理解方式来编译这段代码。
存在第二种理解方式的原因在于,我们的变量是在内存中的,然而参与计算时大部分数据位于CPU的寄存器中。因而,如果我以这样一种方式去实现运算和优化,即将我们认为经常用到的数据存储在寄存器中,以便提高速度,就会变成这种情况。可能的运行过程如下:
编译器发现a在语句中多次出现,于是将a的值直接mov到某个寄存器中,比如EBX,然后开始计算:a/a也就是EBX/EBX(甚至这句没有而直接就是1),将结果1放在EAX中,然后下一步把EBX加到EAX上,就是那个a+=什么什么,然后再把EBX乘到EAX上,结果仍在EAX,最后EBX-EAX赋值给EAX,再将EAXmov到变量a,就得到了-25这样的结果。
当然,上面仅仅是通过分析得到的可能的运行过程,鉴于大部分编译器都不是这样实现的,而且我这里只有gcc环境,没有办法进行测试,因而我们只能说可能存在,但没有实例。
另外需要提醒楼主,所有牵扯到“未定义”(Undefined)行为的语句都是应该在编程中避免的,因为这些语句将依赖于您特定的编译环境。像上面的语句,如果我想要说明白,并且避免未定义行为,就将该拆开的赋值拆开成多个语句,写成这样:
inta=5;
a+=a/a;
a*=a;
a-=a;
也就是尽量保持语句的简单,一个语句中最多一个赋值。(KISS原则)
呵呵,没想到我速度不够快,楼主又增加了几个题目。看到这些我才想起来,这应该是某些测试题上面的东西了吧?很多测试题甚至包括二级考试的题目都会考察一些与编译器实现和特定环境相关的问题,而如果你不是要学习编译原理这种课程,那么这样的考察是没有任何意义的。很多这样的练习题都是,不会对个人的编程能力有任何提升,反倒使得大家经常钻入“未定义”的陷阱出不来。个人观点是,如果想学好编程,自己动手实践是最好的途径。很多东西可以通过实践去学习,比如您编程发现i++没问题但是1++会报错,于是您就知道了哦原来自增运算只能施加于左值(l-value)……当然一些基础的内容还是必要的。实际上后面这几个题目都很基础,这样的问题应该在学习C语言的前期就能够自己解答的。
废话说的不少,本来我看到第一个题目后只是想说明“未定义”行为的一些东西,所以就稀里哗啦写了这么多。但现在看看您下面的问题我觉得您可能只是想要答案而已,——于是我就顺便把其他题目也答了吧,虽然我的建议是您最好自己去发现,通过教材、实践。
第一个:未定义行为,大多数情况下会是0,具体原因前面说的比较清楚了。
第二个:再纠正一个观点,就是这种不确定的问题并不是机器或者系统的问题,而在于编译器的实现。比如我在32位机器上装32位系统,而运行16位的TC,或者64位机器装32位系统,运行16位程序TC,甚至64位机器装64位系统,运行16位TC,最终由TC编译出来的程序一定是被16位TC限定的只能使用寄存器中的AH,AL,AX,BX...而不能使用EAX,EBX,更不用提RAX(当然RAX们只有64位系统和64为程序才用得到),因而设计编译器时只能自然使用AX大小的变量,也就是16位,于是TC中的int长度为16比特或者说两个字节。我想说明的是这些不确定的问题都是编译器实现时造成的,跟系统无关。
好的言归正传,题目要求“值”所占的字节数,语句就是sizeof(a+4.5),然后您可以通过printf将这个结果输出,当然这个结果在大多数32位编译器上应该是8,因为a是整数,但是4.5是双精度浮点数,运算将默认对整数进行类型转换,变成同样的double型后和4.5进行运算。得到的结果当然也是double型,——而几乎所有32位编译器实现中,double型占8个字节。
这里不得不提到的是C99标准的一个可能会造成众多C程序员疑惑的地方,就是浮点数在程序中默认是双精度的,除非强加f标志表示float型。于是,我们可以测试,如果在前面的语句中将4.5这个数字后面加上f标志成为4.5f,那么结果可能是不一样的。事实上,大部分编译器将float规定为4字节,这样经过类型提升后,4字节的int变成4字节的float,和4字节的float运算,得到的仍然是4字节的float,于是就会得到结果为4。
好的我们继续,第三题是基础知识,很明显的C语言中的运算符有左结合与右结合之分,因而A是错误的,故选A。这个就不多说了。
第四题:关于自增和自减,我们从这个运算符的名字就能够看出,它们是针对一个东西的,而且这个东西要可以改变才行。专业一点儿就是,自增和自减运算仅施加于左值,所谓左值通俗讲就是可以放在等号左边的东西(比如变量,但不是全部变量都可以)另外有一种理解是可以在数据段定位(Locate)内存地址的东西,详细点就是可以被赋值的变量,非“只读变量”。或者就是我们通常所说的变量。(注意变量可以用const进行只读修饰。)
给出答案:单目运算符,只能作用在左值上(或者可以说变量,不过不确切),不能作用在右值(两个空就可以填成常量和表达式,但是注意这也是不确切的)。
这个问题很难给出一个确切的答案,——这也是好多这种题目的共同特征,个人感觉没有必要确切到什么地步,只要能理解就行。而C语言中很多标准的地方都是有争议的,比如左值右值的问题。这些在实际编程中真的都不造成影响,但是有些人就愿意把带有这种色彩的不确定性用来考试……
B. c++问题,帮帮忙。。。
额,int a 先在内存中给变量a申请空间 然后a=12,给这个变量赋值,也就是你用到a的时候,编译器会查看在a的内存地址,看看他是否被赋值了
C. C语言 编译器 判断变量作用域的方法
你要注意一点,函数内的 每个{}对的区域都是有一个上级{}的, 确定了这个关系后,在一个子{}内部,判断变量是否定义过的过程就是, 本{}中有没有定义,其上级{}中有没有定义,再上级有没有,如果必要一直判断到全局变量。
这里有一点还要注意, 注意变量定义和{}块的先后关系。
D. 问一下各位,c语言编译器是如何处理变量名的呢
编译器编译到int a;时就在内存中开辟一个两字节的内存空间,并且命名为a
E. C语言问题
虽然当变量没有初始化的时候,变量的值是一个无法确定的垃圾值,但不能认为是把垃圾值赋值给了变量。
这里涉及到一些内存和编译器的知识。当生命变量A的时候,编译器要开辟一块内存空间来存储该变量,比如地址为B。因为内存中的数据是无法预知的,所以分配给A变量的内存地址中的数据是什么也是无法预知的。如果不初始化,则地址B中的数据是随机的,有可能指向其它的地方。因此,从表面上看就相当于把一个随机的垃圾值赋值给了A。
想在好多语言,比如OC,在使用对象的时候,都会用[[object alloc] init]。从而很明确的表达出现申请空间,然后初始化空间的概念。
F. c语言的变量名
变量名不占空间 变量:用来标识(identify)一块内存区域,这块区域的值一般是可以更改的,这就是它“变”的由来,但是我们可以通过使用如const等一些修饰符号来限定这一内存区域的操作特性(characteristic),即变量的操作特性。用const修饰的使变量不能更改的就和常量一样的变量叫做常变量。 变量名:是一个标识符(identifier),用来指代一块内存区域,即变量,使用变量使我们操作内存以区域(area),以块(block)为单位,提高了方便性。 你的机器代码中,是不会出现变量名的;变量名是给我们程序员操作内存来使用的。 想想在汇编年代,没有变量名,我们操作内存,都是用地址来直接操作的,还要控制区域大小;当然汇编语言已经有了简单的变量。 对于编译器,它会搜集我们的变量名,比如我们定义了一个全局的int a;那么编译器都为我们做了什么呢? 它会为程序预留4个字节的空间(假设在32位平台),并把我们的变量名“a”保存进符号表,并用这个符号表的索引对应实际的空间。 如果下面出现b = a;那么它就会根据符号表找到变量的真正的物理位置,取得它的值,赋给b。 这是写编译器需要做的,我们需要建立符号表。 但是实际在汇编层次上,操作的都是地址而已,不存在任何名称了。
满意请采纳
G. 编译系统(如:VC++6.0)编译时对变量和变量地址如何处理
int a =5;//定义一个变量,并初始化
int *p //定义一指针变量
p = &a; //把a的地址赋给指针变量p
int &a = b;//a是b的引用。也就是b的一个别名。只要改变a的值,b的值也就改变了
H. c语言 指针
内存的基本单位是存储单元
一个存储单元占8bit,也就是1Byte
每一个存储单元都一个对应的地址
所谓地址,简单的将就是一个编号,可以理解为每个人的身份证号码一样
指针变量p和变量a的内存是在栈上创建的
两变量都占4Byte,即4个连续的存储单元
&p是指针p的首地址,也就是第一个存储单元的编号(地址)
&a是变量a的首地址,也就是第一个存储单元的编号(地址)
p = &a;的意思是将变量a的首地址赋给变量p,也可以说p保存了变量a的首地址
现在p和&a是一样的地址,那么相同的地址里所存放的内容自然也就一样
用*p或a就可以取该地址里所存放的内容
所以*p和a的内容是一样的
没有分配地址一说,只有分配内存,分配存储单元
地址,他的全称叫物理地址或逻辑地址,是由段基址和偏移量构成,由地址累加器合成物理地址,也就是说每一个存储单元都有一个对应的物理地址,他是规定好的,不存在分配,只有寻址一说
所谓寻址,就是指你所分配的内存的首地址,要看你的内存在什么上创建?(C语言有三种内存创建方式)是该内存的那一段?才能给你相应的首地址,也就是这块内存,这是由编译器决定的,不是你决定的
如果你有兴趣可以去学微机原理和编译原理,比较复杂,牵扯硬件和汇编,还有数电,它会让你更了解这些东西的
以你现在这个基础,只要记住我上面写的那些话就可以了,分清内存,存储单元,地址之间的关系就够了,其他的交给编译器去做,不需要你太多了解,慢慢去学,不可能一口是个胖子,这是一个过程
I. 高分救助:帮我答几道C语言题
转贴的,网络可是个好东东^_^
1.1 这道题主要考察#的功能,S是一个表达式。TRACE()的作用就是在DEBUG状态下,计算表达式S的值之前先打印S。
1.2 #error用于向编译器报错,并输出它后面带的错误信息。例如:
#ifndef SOMETHING
#error SOMETHING not defined!
#endif
如果在这段代码之前未定义过SOMETHING,则在编译时出错,并给出"SOMETHING not defined!"的错误信息。
作者: 增压Q因斯坦 2007-1-12 06:20 回复此发言
--------------------------------------------------------------------------------
2 偶素新来的,请吧主多多关照
1.3 #define NELEMENTS(array) (sizeof(array) / sizeof((array)[0]))
1.4 #define OFFSET(structure, member) ((int) &(((structure *)0)->member))
2 (a) An integer:int a;
(b) A pointer to an integer:int *a;
&; A pointer to a pointer to an integer:int **a;
(d) An array of 10 integers:int a[10];
(e) An array of 10 pointers to integers:int *a[10];
(f) A pointer to an array of 10 integers:int (*a)[10];
(g) A pointer to a <I>function</I> that takes an integer as an argument and returns an integer:int (*a)(int);
(h) An array of 10 pointers to <I>function</I>s that take an integer argument and return an integer:int (*a[10])(int);
3 char (*(*x())[])();
这道题来自"The C Programming Language"中的一个例子。
首先,确定标识符:x
x是一个函数,没有参数:x()
返回值是一个指针:*x()
这个指针指向一个数组:(*x())[]
数组中的每个元素是指针:*(*x())[]
指向一个不带参数的函数:(*(*x())[])()
函数的返回值是char:char (*(*x())[])()
这里,要知道*、()和[]的优先级。
4 这个定义有点怪,它的意思是:jmp_buf这种类型是一个数组,只有一个元素,元素类型为struct{...}。数组名作为函数参数时,应该是传递地址/指针。
5 在编译源文件时,C编译器和C++编译器都会对符号(函数或变量)名作某些修正,但两者采用的修正方法不同,所以两者生成的目标文件不能互相链接。在C+ +中使用extern "C"可以让C++符号获得C链接特性。由于C++编译器会自动定义__cplusplus宏,所以在C语言头文件中采用这种结构可以保证无论使用何种编译器,生成的目标文件都具有C链接特性,能够与标准C编译器所生成的目标文件相链接。
6 (1)用于全局变量:外部静态变量,只能在本源文件中被引用,不能被其它源文件所引用。
(2)用于局部变量:局部静态变量,在函数返回后存储单元不释放;下一次调用该函数时,该变量为上次函数返回时的值。
(3)用于函数:内部函数,只能被本源文件中的函数所调用,不能被其它源文件调用。
7.1 const关键字在C语言中用于声明"常变量",其值不可修改,但具有确定的数据类型。C编译器总是为其分配相应的存储单元。
在C++中,const关键字用于声明常量,C++编译器视具体情况决定是为其分配存储单元还是仅将其作为编译期间的常量。
7.2 const int a1; a1是整型常量。
int const a2; a2是整型常量。等同于const int a2;
const int *a3; a3是指针(a3是可变的),指向一个整型常量。等同于int const *a3;
int * const a4; a4是常量指针(a4不可变),指向一个整型变量。
int const * const a5; a5是常量指针(a5不可变),指向一个整型常量。等同于const int * const a5;
8.1 volatile关键字用于声明内存映射的易失型变量,这类变量的值随时可能由于某种编译器所不知道的原因(例如,外部设备对其写入)所改变,所以编译器在进行代码优化时不能对其做任何的假设和依赖。
8.2 volatile可以和const一起使用,不过很少见。
const关键字的意思是限制编程者自己不能修改变量的值;两者并不矛盾。
例如一个内存映射的、只读的硬件寄存器,假设它的地址是p,则可以这样声明:volatile const UINT32 *p;
9 sizeof(pmsg) = 指针变量的长度
sizeof(msg) = 2 (字符数组的长度)
sizeof("A") = 2 (字符串的长度)
sizeof(ch) = 1 (字符变量的长度)
sizeof(‘A’) = 整型变量的长度 (在C语言中,字符常量的数据类型实际上是int;在C++中,它的数据类型是char,从而原式等于1)
sizeof(param) = 指针变量的长度 (数组名作参数时,传递的是数组的起始地址)
10 这种写法是和编译器&操作系统相关的,所以不应当这样写。在WIN2K+VC环境下debug程序时会出现异常。
作者: 增压Q因斯坦 2007-1-12 06:20 回复此发言
--------------------------------------------------------------------------------
3 偶素新来的,请吧主多多关照
不过这样写,编译器不会报错。按道理,"hello..."的类型是const char [N],它是不能赋值给char *的,
因为会丢失常量属性。但在const关键字成为C标准之前,大家都这样写程序,所以char *pmsg = "hello..."
这种写法被给予特别豁免,即使在C++中也是如此,在"The C++ Programming Language"的附录里对此有讨论。
"hello, world!"是字符串常量(string literal),它的类型是const char [N],N为字符串的长度(包括结尾的0)。
"The C Programming Language"指出,写字符串常量的结果是未定义的(undefined)。所以在有些平台(操作系统+编译器)
上程序不会出错,而在其它平台上程序出现异常。
GNU手册里这样说:
Writing into string constants is a very bad idea; "constants" should be constant.
不过在GNU中它提供另外的选择:使用-fwritable-strings进行编译就可以。
那么,为什么不允许修改字符串常量呢(它不也在内存中吗)?
这可能和另外一个特点有关,即重复字符串的合并。现在的编译器应该会主动帮助我们合并程序中相同的字符串常量
以节省内存。如果string literal可写,就会出现问题。例如:
void foo()
{
printf("%s\n", "how are you?");
}
void bar()
{
char *p = "how are you?";
strcpy(p, "WHO ARE YOU?");
}
调用foo()当然会打印"how are you"。但如果编译器合并字符串,那么先调用bar(),再调用foo(),foo()打印的就是
"WHO ARE YOU?"。这当然不是我们想要的结果。
另外一方面,这样写也有问题(确实有人这么写):
if (func() == "something")
...
func()是:
char *func()
{
...
return "something";
}
这就假设编译器一定会帮我们合并字符串,然而那也不一定。
11 输出"> 6"。
混合运算时的数据类型转换次序:int --> unsigned --> long --> double。
另外,char和short必定转换为int,float必定转换为double。
12 p = (int *)((unsigned int)a + 1);
代码的意图是想使p指向数组的第二个元素,但通常的写法是:p=a+1。这里存在这样的问题:a是个常量地址,
a+1指向下一个数组元素,而((unsigned int)a + 1)指向下一个内存地址。如果地址是字节计数的,则p指向的
是数组第一个元素的第二个字节。还有一个效果就是:在RISC上该printf语句会出异常,因为不允许非对齐访问
(mis-aligned access)。对齐访问就是访问2字节变量的地址要能被2整除,4字节变量的地址要能被4整除,etc。
13 这些函数到处都查得到,就不用做了吧.
【2 数据声明和定义】
给定以下类型的变量a的定义式:
a) An integer int i = 0;
b) A pointer to an integer int *p = NULL;
c) A pointer to a pointer to an integer int **pp = NULL;
d) An array of 10 integers int a[10];
e) An array of 10 pointers to integers int *pa[10];
f) A pointer to an array of 10 integers int *ap = a;
g) A pointer to a <I>function</I> that takes an integer as an argument and returns an integer int ( *fp)( int )=NULL;
h) An array of ten pointers to <I>function</I>s that take an integer argument and return an integer typedef int( *fptype )( int );
fptype fpa[10];
【3 复杂类型(1)】
有如下表达式:
char (*(*x())[])();
请用文字描述x是什么?
回答:声明x是一个函数指针数组,函数的返回值类型是字符指针,参数为空。
空指针同志在《解释一下 typedef int (*fn_ptr)()》这篇帖子里说过
c专家编程中理解c语言声明的优先级原则,如下:
1) 声明从它的名字开始读取,然后按照优先级顺序依次读取
2) 优先级地从高到低的顺序是
a)声明中括号被括起来的部分
b)后缀操作符:
括号()表示这是一个函数,方括号表示这是一个数组
作者: 增压Q因斯坦 2007-1-12 06:20 回复此发言
--------------------------------------------------------------------------------
4 偶素新来的,请吧主多多关照
c)前缀操作符,*表示这是一个指针
3)如果有const或volatile后面紧跟类型说明符如int等,那么它作用于类型说明符,在其它情况下,const等作用于紧邻的指针星号。
还真派上用场了,特致谢,不知对不对。
【4 复杂类型(2)】
jmp_buf的定义:
typedef struct _jmp_buf
{
REG_SET reg;
int extra[3];
} jmp_buf[1];
setjmp函数的原型:
extern int setjmp (jmp_buf __env);
问:调用setjmp时传递__env的内容,还是传递指针?
答:传递指针。由jmp_buf 的定义来看,它是一个数组类型,并且只含一个元素。由此_env是一个数组,在函数的形参中,任何形式的数组都是指针等价的,传递参数时数组名将转化为相应的指针类型。实际上,在所有表达式中都是如此。
为什么要把jmp_buf定义为数组呢?因为setjmp需要改变__env的内容,这必须要用指针才行。但为什么不直接把参数定义为指针类型而是用这样一种奇特的方法,我也不清楚。
【5 头文件】
问:为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
这个不太会,但试一试。
答:为了使c++兼容c,在c++中也能调用c的标准库。如果定义了__cplusplus测试宏,则编译时进行extern "C"声明。
#ifndef __INCvxWorksh
#define __INCvxWorksh
这俩是什么不清楚。
【6 static关键字】
请说出static关键字的3种用处:
(1)用于全局变量;
(2)用于局部变量;
(3)用于函数。
/* file.c */
static int a; /* a只能在file.c中被引用*/
int b;
static int fn() /*fn只能在file.c中被引用*/
{
static int x; /* x的生存周期和程序的生存周期一样 */
int y;
}
答:见注释。
7.1 const关键字的意义是什么?
7.2 解释以下的变量定义:
const int a1; //a1为整型常量
int const a2; //同上
const int *a3; //a3整型指针常量,a3本身是个常量
int * const a4; //a4是整型常量指针,a4所指内容是常量
int const * const a5; //a5是整型常量指针常量,a5本身和a5所指内容都是常量
晕了
【9 sizeof()】
有以下定义:
char *pmsg = "A";
char msg[] = "A";
char ch = 'A';
答:
sizeof(pmsg) = 4 //32位编译器
sizeof(msg) = 2 //字符'A'和'\0'
sizeof(“A”) = 2 //同上
sizeof(ch) = 1
sizeof(‘A’) = 1 (在C++中等于多少?)
void f(char param[100])
{
// sizeof(param) = 4 //32位编译器
}
评论Feed
J. 11.变量定义并初始化正确的是( )
A中 定义了变量a设定初值I,只有假设变量I是已经定义过的变量语句才有效,其次定义了指针p,整型指针应该用于保存整型变量的地址,将变量a的内容送入指针,语法不错,但逻辑意义是错的。
B中,a是整型变量,*a的计算方式错误,*应用用于对指针的操作
C中,定义的p是整型变量,不应该用于保存变量的地址
只有D是完全正确的。