首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C语言 >

面试疑问,关于未定义行为和常量字符串,请和老软件工程师打过交道的进来指点

2013-09-26 
面试疑问,关于未定义行为和常量字符串,请和老程序员打过交道的进来指点本帖最后由 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 是否这个意思。






 

热点排行