面试疑问,关于未定义行为和常量字符串,请和老程序员打过交道的进来指点本帖最后由 xinhua0910 于 2013-09-
面试疑问,关于未定义行为和常量字符串,请和老程序员打过交道的进来指点 本帖最后由 xinhua0910 于 2013-09-06 22:52:09 编辑 过程: 今天去K公司面试,结果别人老总(40多岁)说我最后几个基础知识题都做错了,我很好奇是哪几个,就问他,他说最后一个题就错了... 最后一题: 请说明下列表达式是否正确,如果正确请写出最后的a值 1. a += a++; 2. a++ = a; 3. a-- = a++; 我给出的结果是:都不正确,属于“未定义行为”,不同编译器不同环境可能得到不同结果,属于不良写法。 请问下:我很好奇,是我真的回答错了,还是他们老总自己搞错了。 这让我想到我以前的一个公司培训,当时是一个6、70岁的老前辈给我们培训。当讲到如下例子,问结果是什么时,我回答是可能程序崩溃,因为试图修改常量字符串。他当时说我的不对,是输出“hallo world”,还用他的虚拟机做了演示,居然还真得出结果了! char *p = "hello world!"; p[1] = 'a'; printf("%s\n", p); 我想了解: 是不是以前的老的C标准对上述“未定义行为”和“常量字符串”的处理有一个确定的处理方法,导致他们如此确信这些程序是对的、可行的。 未定义行为 常量字符串 c [解决办法] 2,3肯定不是未定义行为,确定为编译时错误。这么说来,您是回答错了,还是老总厉害。[解决办法] 对于1我感觉你的回答应该是正确的 2,3因为在赋值运算符的左侧只能是变量 而这里是表达式 应该是错误的行为 对于字符串那个 我觉得你的回答应该也是正确的 至少我的理解是这样 期待大牛们出来说明下[解决办法] 这是我在gcc下测试的2,3时提示的错误信息 "赋值运算的左操作数必须是左值"[解决办法] 嗯,看编译器怎么看待字符串常数了。要是放在ReadOnly的数据区,就不能修改了。
引用: Quote: 引用: 字符串的那个,如果是const char *,LZ的回答应该是正确的,没有的话,面试官是对的。Quote: 引用: 对于1我感觉你的回答应该是正确的 2,3因为在赋值运算符的左侧只能是变量 而这里是表达式 应该是错误的行为 对于字符串那个 我觉得你的回答应该也是正确的 至少我的理解是这样 期待大牛们出来说明下 字符串这个,肯定是有问题的,没有const一样有问题。虽然在aix下可能不会出错,你拿到linux下cc然后跑跑试试,肯定“Segmentation fault”[解决办法] 2.13 以下的初始化有什么区别?char a[] = "string literal"; char *p = "string literal"; 当我向 p[i] 赋值的时候, 我的 程序崩溃了。 字符串常量有两种稍有区别的用法。用作数组初始值 (如同在 char a[] 的声明中), 它指明该数组中字符的初始值。其它情况下, 它会转化为一个 无名的静态字符数组, 可能会存储在只读内存中, 这就是造成它不一定能被修改。 在表达式环境中, 数组通常被立即转化为一个指针 (参见第 6 章), 因此第二个声明把 p 初始化成 指向无名数组的第一个元素。
为了编译旧代码, 有的编译器有一个控制字符串是否可写的开关。[解决办法]
引用: 1不是未定义行为,相当于a += a; a++; a += a++;
C99 里面是未定义行为哦
不清楚不要乱说
[解决办法] 很佩服那个六七十岁的老前辈,值得尊敬。
但是在这问题上他真的老了。
老的编译器通常把字符串常量放在数据段(.data)
新的编译器通常把字符串常量放在只读数据段(.rodata)或者代码段(.text), 一般还有选项可以控制。
比如:
AIX下 IBM XL C/C++ compiler编译器关于字符串常量的控制。
-qro
[解决办法] -qnoro
Specifies the storage type for string literals and
puts string literals in read-only storage.
Default:
o -qnoro with cc and its derivatives.
o -qro otherwise.
说明一下
下面的cc, xlc, c89, c99都是IBM XL C/C++编译器,只是有不同的缺省选项。
AIX5.3$ cc -o demo demo.c && ./demo
hallo
AIX5.3$ cc -qro -o demo demo.c && ./demo
Memory fault(coredump)
AIX5.3$ xlc -o demo demo.c && ./demo
Memory fault(coredump)
AIX5.3$ c89 -o demo demo.c && ./demo
Memory fault(coredump)
AIX5.3$ c99 -o demo demo.c && ./demo
Memory fault(coredump)
AIX5.3$ cat demo.c
#include <stdio.h> int main(void) { char *p = "hello"; p[1] = 'a'; printf("%s\n", p); return 0; }[解决办法] 第一题没回答到点子上啊,明明已经有两个语法错了,你却才说未定义行为。所以还是你做错了。
[解决办法]
引用: char *p = "hello world!"; p[1] = 'a'; printf("%s\n", p); 这个要看编译器的内存分配。 一般来讲,"hello world!"属于一个常量的字符串,定义在常量存储去,其值是不能更改的, 我在VC++上运行崩溃 如果是 char p[] = "hello world!"; p[1] = 'a'; printf("%s\n", p); 则可以正常运行,因为数组一般定义在栈上。 char *p = "hello world!";
p[1] = 'a';
printf("%s\n", p);
debug 崩溃
release 正常 ,你试试
至少VC6 是这样的.
VC6 release 输出
hallo world!
楼主的问题:
1. a += a++;
//这个可以不必未定义,看标准,看编译器,
//因为这个式子有一个唯一答案,而不是摸棱两可的,只是结果有点怪异.
// 换成a=a++;也一样.
2. a++ = a; //错误,a++结果是个右值,不可作为左值使用
3. a-- = a++;//错误,a--结果是个右值,不可作为左值使用
VC 6
:
int a=10;
printf("a=a++ =%d \n",a=a++ );
printf("%d \n",a);
输出 :
a=a++ =10
11
关于未定义,应该是至少某些编译器,不认为是错误,而标准并没有规定应该如何处理的情况;
大多数都是每个编译器都不认为是错误,但是每个编译器编译的结果都不同,或者是会出现运行时错误的行为。
对于确凿无疑的错误,任何一个编译器都不会姑息得,编译不了的程序,显然不是未定义行为
有些情况,不同标准是不一样的,因为标准是不断发展的。
当然
可能有些早期的标准认为正常的行为,新标准规定为未定义行为。
[解决办法] char *p = "hello"; 估计VC这样的编译器,认为"hello";
应该放到常量区,
所以debug 不允许改动,因此不可以调试代码,吓退那些想修改常量的程序员.
不过,release 由于代码优化,还是放在数据区更好(产生和捕捉异常是代价高昂的操作).
所以还是可以改动的.
如果,"hello"; 应该放到常量区,是标准的话,那么这种编译器,只执行标准的一半.
好处是,老的类似的代码,还是可以执行的,从而达到代码兼容的目的.
新写的代码,由于调试不能通过,于是符合标准,可以吓阻,不执行新标准的程序员.
于是,即达到执行新标准的目的,又可以兼容旧的代码.
同时,还对release进行了代码优化.
不知 MS 是否这个意思。