精华内容
下载资源
问答
  • C语言未定义行为

    千次阅读 2018-04-08 16:58:16
    写在前面的话: ...感谢博主,是真的结合自己的感受写出来的,而且练习题哈哈哈和我遇上的也...简单地说,未定义行为是指C语言标准未做规定的行为。编译器可能不会报错,但是这些行为编译器会自行处理,所以不同的编译...

    写在前面的话:
    原文链接为:https://blog.csdn.net/qq_29169813/article/details/51416281。感谢博主,是真的结合自己的感受写出来的,而且练习题哈哈哈和我遇上的也是一样的。我申请过啦,博主同意我转载的哦~

    1.什么是未定义行为

    简单地说,未定义行为是指C语言标准未做规定的行为。编译器可能不会报错,但是这些行为编译器会自行处理,所以不同的编译器会出现不同的结果,什么都有可能发生,这是一个极大的隐患,所以我们应该尽量避免这种情况的发生。

    1.1特征

    1. 首先定义不确定副作用行为:

      粗略而言:是指在同一个表达式中同一对象修改一次以上的行为。
      诸如:同一变量被修改以后又进行了引用的自增, 自减和赋值

    2. 未定义行为是指:包含多个不确定的副作用的代码的行为

    在网上了解了一番,发现未定义行为有很多,而我初出茅庐,遇到的情况不多,只有借鉴前人的经验。总结了一些前人遇到的问题。下面三种未定义行为是前人总结的,我只是加上了一点自己的理解。有错误望指出。
    附原文地址:http://www.itoldme.net/archives/904

    1.1.1第一例(同一个表达式中有多种运算符)

    同一个表达式中多种运算符一起计算的时候,即使我们知道各符号都有自己的优先级或者是人为的加上括号限制计算顺序,但是我们却不知道编译器会先计算哪一段,计算顺序完全取决于编译器,所以结果并不一定按照我们预想中的输出。

    代码段一:

    int i=7; 
    printf(“%d”, i++*i++); 

    编译器可能选择变量的旧值相乘以后再对二者进行自增运算,但是无法确保自增或自减一定会在1.输出变量原值之后,2.对表达式的其它部分计算之前立即进行。

    代码段二

    int a=5,b;
    b=++a*–a;

    b的值不能确定

    代码段三

     int i = 5;
     int j = (++i) + (++i) + (++i);

    j j <script type="math/tex" id="MathJax-Element-9">j</script> 的值不能确定

    代码段四

    #include <stdio.h>
    int main(){
        int i = 0;
        int a[] = {10,20,30};
    
        int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];
        printf("%d\n", r);
        return 0;
    }

    这段代码也并不是我们想象中的那样按照优先级来计算,编译器选择了他自己的一种套路,
    此段代码详细实现情况请戳链接:http://blog.jobbole.com/53211/

    1.1.2 第二例(同一语句中各参数的求值顺序)

    在同一语句中,有多个表达式,我们不能确定编译器先调用哪一个表达式进行运算,运算之后又会对另一个表达式产生影响,因为他不一定是按照我们想象中自左向右进行调用的。
    代码段一

     printf("%d,%d\n",++n,power(2,n)); 

    代码段二

    int f(int a, int b);
    int i = 5;
    f(++i, ++i);

    1.1.3第三例(通过指针修改const常量的值)

    编译器对于向常量所在内存赋值这件事的处理是未定义的。即在对常量的内存操作也许并不是我们想象的那样。
    代码段一

    int main()
    {
        const int a = 1;
        int *b = (int*)&a;
        *b = 21;
    
        printf("%d, %d", a, *b);
        return 0;
    }

    该段代码的详细实现请戳链接:http://www.cnblogs.com/wghost/p/3280074.html

    练习题

    这里写图片描述

    解析:根据上面的总结,A选项,我的理解是我们不知道编译器会怎么选择自增和赋值的顺序,所以这是由编译器决定的,属于未定义行为。B选项,”hello“这个字符串属于一个字符串常量了,指针p指向了这个字符串常量,下一语句通过这个指针来直接修改常量第二个字符,这也属于未定义行为。选项C,只是通过指针找到第二个字符并将它赋值给一个字符变量,并没有改变这个字符串常量,所以不属于未定义行为。选项D,在printf语句中,i++和i–谁先执行由编译器决定,这是未定义行为。故此题选C。

    展开全文
  • C语言未定义行为一览

    2021-05-21 04:58:53
    最近我们一直在互相考问C语言的知识,所以我微笑着鼓起勇气面对无疑即将到来的地狱。他在白板上写了几行代码,并问这个程序会输出什么?#include int main(){int i = 0;int a[] = {10,20,30};int r = 1 * a[i++] + 2...

    编者注:文章中的"我"是指原文作者

    几周前,我的一位同事带着一个编程问题来到我桌前。最近我们一直在互相考问C语言的知识,所以我微笑着鼓起勇气面对无疑即将到来的地狱。

    他在白板上写了几行代码,并问这个程序会输出什么?

    #include

    int main(){

    int i = 0;

    int a[] = {10,20,30};

    int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];

    printf("%d\n", r);

    return 0;

    }

    看上去相当简单明了。我解释了操作符的优先顺序——后缀操作比乘法先计算、乘法比加法先计算,并且乘法和加法的结合性都是从左到右,于是我抓出运算符号并开始写出算式。

    int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];

    // = a[0] + 2 * a[1] + 3 * a[2];

    // = 10 + 40 + 90;

    // = 140

    我自鸣得意地写下答案后,我的同事回应了一个简单的“不”。我想了几分钟后,还是被难住了。我不太记得后缀操作符的结合顺序了。此外,我知道那个顺序甚至不会改变这里的值计算的顺序,因为结合规则只会应用于同级的操作符之间。但我想到了应该根据后缀操作符都从右到左求值的规则,尝试算一遍这条算式。看上去相当简单明了。

    int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];

    // = a[2] + 2 * a[1] + 3 * a[0];

    // = 30 + 40 + 30;

    // = 100

    我的同事再一次回答说,答案仍是错的。这时候我只好认输了,问他答案是什么。这段短小的样例代码原来是从他写过的更大的代码段里删减出来的。为了验证他的问题,他编译并且运行了那个更大的代码样例,但是惊奇地发现那段代码没有按照他预想的运行。他删减了不需要的步骤后得到了上面的样例代码,用gcc 4.7.3编译了这段样例代码,结果输出了令人吃惊的结果:“60”。

    这时我被迷住了。我记得,C语言里,函数参数的计算求值顺序是未定义的,所以我们以为后缀操作符只是遵照某个随机的、而非从左至右的顺序,计算的。我们仍然确信后缀比加法和乘法拥有更高的操作优先级,所以很快证明我们自己,不存在我们可以计算i++的顺序,使得这三个数组元素一起加起来、乘起来得到60。

    现在我已对此入迷了。我的第一个想法是,查看这段代码的反汇编代码,然后尝试查出它实际上发生了什么。我用调试符号(debugging symbols)编译了这段样例代码,用了objdump后很快得到了带注释的x86_64反汇编代码。

    Disassembly of section .text:

    0000000000000000 :

    #include

    int main(){

    0: 55 push %rbp

    1: 48 89 e5 mov %rsp,%rbp

    4: 48 83 ec 20 sub $0x20,%rsp

    int i = 0;

    8: c7 45 e8 00 00 00 00 movl $0x0,-0x18(%rbp)

    int a[] = {10,20,30};

    f: c7 45 f0 0a 00 00 00 movl $0xa,-0x10(%rbp)

    16: c7 45 f4 14 00 00 00 movl $0x14,-0xc(%rbp)

    1d: c7 45 f8 1e 00 00 00 movl $0x1e,-0x8(%rbp)

    int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];

    24: 8b 45 e8 mov -0x18(%rbp),%eax

    27: 48 98 cltq

    29: 8b 54 85 f0 mov -0x10(%rbp,%rax,4),%edx

    2d: 8b 45 e8 mov -0x18(%rbp),%eax

    30: 48 98 cltq

    32: 8b 44 85 f0 mov -0x10(%rbp,%rax,4),%eax

    36: 01 c0 add %eax,%eax

    38: 8d 0c 02 lea (%rdx,%rax,1),%ecx

    3b: 8b 45 e8 mov -0x18(%rbp),%eax

    3e: 48 98 cltq

    40: 8b 54 85 f0 mov -0x10(%rbp,%rax,4),%edx

    44: 89 d0 mov %edx,%eax

    46: 01 c0 add %eax,%eax

    48: 01 d0 add %edx,%eax

    4a: 01 c8 add %ecx,%eax

    4c: 89 45 ec mov %eax,-0x14(%rbp)

    4f: 83 45 e8 01 addl $0x1,-0x18(%rbp)

    53: 83 45 e8 01 addl $0x1,-0x18(%rbp)

    57: 83 45 e8 01 addl $0x1,-0x18(%rbp)

    printf("%d\n", r);

    5b: 8b 45 ec mov -0x14(%rbp),%eax

    5e: 89 c6 mov %eax,%esi

    60: bf 00 00 00 00 mov $0x0,%edi

    65: b8 00 00 00 00 mov $0x0,%eax

    6a: e8 00 00 00 00 callq 6f

    return 0;

    6f: b8 00 00 00 00 mov $0x0,%eax

    }

    74: c9 leaveq

    75: c3 retq

    最先和最后的几个指令只建立了堆栈结构,初始化变量的值,调用printf函数,还从main函数返回。所以我们实际上只需要关心从0×24到0×57之间的指令。那是令人关注的行为发生的地方。让我们每次查看几个指令。

    24: 8b 45 e8 mov -0x18(%rbp),%eax

    27: 48 98 cltq

    29: 8b 54 85 f0 mov -0x10(%rbp,%rax,4),%edx

    最先的三个指令与我们预期的一致。首先,它把i(0)的值加载到eax寄存器,带符号扩展到64位,然后加载a[0]到edx寄存器。这里的乘以1的运算(1*)显然被编译器优化后去除了,但是一切看起来都正常。接下来的几个指令开始时也大致相同。

    2d: 8b 45 e8 mov -0x18(%rbp),%eax

    30: 48 98 cltq

    32: 8b 44 85 f0 mov -0x10(%rbp,%rax,4),%eax

    36: 01 c0 add %eax,%eax

    38: 8d 0c 02 lea (%rdx,%rax,1),%ecx

    第一个mov指令把i的值(仍然是0)加载进eax寄存器,带符号扩展到64位,然后加载a[0]进eax寄存器。有意思的事情发生了——我们再次期待i++在这三条指令之前已经运行过了,但也许最后两条指令会用某种汇编的魔法来得到预期的结果(2a[1])。这两条指令把eax寄存器的值自加了一次,实际上执行了2a[0]的操作,然后把结果加到前面的计算结果上,并存进ecx寄存器。此时指令已经求得了a[0] + 2 * a[0]的值。事情开始看起来有一些奇怪了,然而再一次,也许某个编译器魔法在发生。

    3b: 8b 45 e8 mov -0x18(%rbp),%eax

    3e: 48 98 cltq

    40: 8b 54 85 f0 mov -0x10(%rbp,%rax,4),%edx

    44: 89 d0 mov %edx,%eax

    接下来这些指令开始看上去相当熟悉。他们家在i的值(仍然是0),带符号扩展至64位,加载a[0]?到edx寄存器,然后拷贝edx里的值到eax。嗯,好吧,让我们在多看一些:

    46: 01 c0 add %eax,%eax

    48: 01 d0 add %edx,%eax

    4a: 01 c8 add %ecx,%eax

    4c: 89 45 ec mov %eax,-0x14(%rbp)

    在这里把a[0]自加了3次,再加上之前的计算结果,然后存入到变量“r”。现在不可思议的事情——我们的变量r现在包含了a[0] + 2 * a[0] + 3 * a[0]。足够肯定的是,那就是程序的输出:“60”。但是那些后缀操作符上发生了什么?他们都在最后:

    4f: 83 45 e8 01 addl $0x1,-0x18(%rbp)

    53: 83 45 e8 01 addl $0x1,-0x18(%rbp)

    57: 83 45 e8 01 addl $0x1,-0x18(%rbp)

    看上去我们编译版本的代码完全错了!为什么后缀操作符被扔到最底下、所有任务已经完成之后?随着我对现实的信仰减少,我决定直接去看源代码。不,不是编译器的源代码——那只是实现——我抓起了C11语言规范。

    这个问题处在后缀操作符的细节。在我们的案例中,我们在单个表达式里对数组下标执行了三次后缀自增。当计算后缀操作符时,它返回变量的初始值。把新的值再分配回变量是一个副作用。结果是,那个副作用只被定义为只被付诸于各顺序点之间。参照标准的5.1.2.3章节,那里定义了顺序点的细节。但在我们的例子中,我们的表达式展示了未定义行为。它完全取决于编译器对于 什么时候 给变量分配新值的副作用会执行 相对于表达式的其他部分。

    最终,我俩都学到了一点新的C语言知识。众所周知,最好的应用是避免构造复杂的前缀后缀表达式,这就是一个关于为什么要这样的极好例子。

    展开全文
  • 对于未定义行为,C++标准没有明确规定编译器们应该怎么做,那么执行的结果就是不可预料的。下面我们来详细探讨下
  • 摘 要:自增自减运算符是C语言的一个特色,本文通过表达式中对一个变量进行多次自增或自减运算时产生的未定义行为进行了详细介绍,望帮助大家正确使用自增自减运算符。关键词:C;自增自减运算符;未定义行为中图...

    摘 要:自增自减运算符是C语言的一个特色,本文通过表达式中对一个变量进行多次自增或自减运算时产生的未定义行为进行了详细介绍,望帮助大家正确使用自增自减运算符。

    关键词:C;自增自减运算符;未定义行为

    中图分类号:TP312 文献标识码:A 文章编号:1674-7712 (2014) 02-0000-01

    一、自增自减运算符

    C语言提供了两个用于算术运算的单目运算符:自增(++)和自减(--)。有两种用法:前置运算(先增减后运算),后置运算(先运算后增减)。下类代码段:

    在不同的编译器上运行会有不同的结果:

    一种是:21,8,一种是:22,8,一种是24,8;

    很奇怪吧,一样的表达式不同的编译器,运行结果会不同,虽然可以通过Debug反汇编分析出不同,那为什么会这样?哪个是正确答案?遗憾的是这样的题目是并没有答案,如果真的要给一个答案,可以是未定义行为。

    二、C语言中未定义行为(Undefined behavior)

    未定义行为是一个非常微妙的话题,在C中许多貌似合理的行为实际上都有着不确定的行为,并且这通常是程序中BUG的源头。未定义行为,就是C标准没有对其进行定义,程序员不能预测会发生什么事的计算机代码,编译器可以随意进行计算,简单说就是语言规格在定义时为了编译器工作的弹性和效率,会刻意不去规定某些规格,如果我们的程序依赖这些没有规定的特性时,就称之为未定义行为,这样的程序语句在不同的编译器上编译会得到不同的结果。另外由“实现”定义的行为,这里的实现指的是C的不同编译器,也是未定义行为。

    C中的自增自减运算就属于未定义行为,因为C没有规定i++或者++i的加1动作到底发生在一个语句的那个时刻(序列点)执行,所以不同编译器在不同的序列点上执行加1动作就可能有不同的结果。尽管后缀前缀自增自减操作符++和--分别在输出其旧值之后和输出新值之前执行加1减1运算,但这里的“之后”常常被误解。因为没有任何保证确保后置前置自增自减会在输出变量原值之后以及输出新值之后会对表达式的其它部分进行计算之前以及之后立即进行,也不能保证变量的更新会在表达式完成之前的某个时刻进行,这样就导致结果的不同。这样导致未定义行为会包含多个不确定的副作用,上述表达式是有副作用的,因为在序列点之间,变量的值不能保证行为唯一性,也就是说第一次执行完++i,它在内存中的值可能改变也可能不变。

    三、未定义行为的副作用

    不确定副作用是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增,自减和赋值操作符的任何组合。C语言允许在一个表达式中使用一个以上的赋值类运算,包括赋值运算符、自增运算符、自减运算符等。这种灵活性使程序简洁,但同时也会引起副作用。副作用主要表现在:使程序费解,并易于发生误解或错误。除此之外,C中的任何的未定义行为都有可能(无论是编译期还是运行期)产生一些格式化你的硬盘的、做一些你完全想象不到的事情的代码,所以不要试图探究这些东西在你的编译器中是如何实现的(这与我们C教科书上的许多练习正好相反)。另外C/C++规定,任何依赖于特定计算顺序、依赖于在顺序点之间实现修改效果的表达式,其结果都没有保证,因此如果在任何完整表达式里存在对同一变量的多次引用,那么表达式里就不应该出现对这一变量的副作用,否则就不能保证得到预期结果,还有这不是在某个系统里试一试的问题,因为我们不可能试验所有可能的表达式组合形式以及所有可能的上下文。但这类表达式在教材中很多,难道只是未定义行为的副作用,再来研究一下这类表达式的合法性。

    四、表达式的合法性

    计算机科学家Brian W.Kernighan主编的《The C Programming Language》明确指出:自增与自减运算符只能作用于变量,不能应用于表达式以及整数,例如++(a+b)与10++,const int N=0;N++;都是非法的。

    为什么?因为这些表达式实际上包括了一个赋值运算,a++等价于a=a+1;C语言中赋值运算符存在左值,只能变量可以作为左值,表达式以及整数不能作为左值,因为它们没有确定的内存地址。因此++(a+b)等价于(a+b)=(a+b)+1是不允许的,以此分析,(a++)+(a++)这个表达式中的后一项是非法的,表面上它只是一个a++,但是在第一个a++的作用下,a已经变成a=a+1;因此第二个的a++则成为(a+1)++了,这是非法的。一般地,在一个表达式中,对同一个变量进行多次自增或自减运算都是非法的。C语言之父Dennis M.Ritchie还提醒,编译器应在这种情况下会给出警告,事实上Linux平台下的编译器GCC确实会对此给出警告(VC++6.0没有):Warning:operation on‘a’may be undefined;

    C++Primer解释:使用了未定义行为的程序都是错误的,即使程序能运行也只是巧合,未定义行为源于编译器不能检测到的错误或太麻烦以至无法检测的错误。然而,含有未定义行为的程序在某些编译器中可以正确执行,但并不能保证同一程序在不同编译器中甚至在当前编译器的后继版本中会继续正确运行,也不能保证程序在一组输入上正确运行并在另一组输入上也正确运行。总而言之,绝对不要写这种表达式,否则或早或晚会在某种环境中遇到麻烦。因此书写程序时,要尽可能的保持清晰易懂,代码除了供人阅读以外,简单清晰的程序也不容易出错。

    五、未定义行为的使用

    既然C、C++、底层虚拟机(LLVM)IR中都有未定义行为,那么肯定有意义,现代计算机是以二进制逻辑运算为基础,“对于确定的输入,其输出是确定的”,行为必然是确定的。但是,如果不为计算机的基础计算模型引入不确定性,人工智能(AI)不可能真正实现。另外为了使C应用程序获得更好的性能,可以通过优化编译器产生高性能,而未定义行为使优化成为可能。

    六、结束语

    在教科书以及等级考试中,对一个变量在表达式中多次自增自减很常见,本文针对表达式运行结果不同的疑惑进行了解答,详细介绍了表达式中涉及到的未定义行为,希望对大家有正确的指导意义。

    参考文献:

    [1]谭浩强.C语言程序设计[M].北京:清华大学出版社,2011.

    [2]黄玉兰.有关C语言输出函数中的自增自减运算符在不同编译环境中的探讨[J].科技致富向导,2013(20).

    展开全文
  • 在一些程序语言中,在某些情况下存在着一些未定义行为,其中以C和C++最为著名,在C++中,规定某些操作的行为是未定义的。这些未定义行为往往会导致一些程序的错误问题的出现,作为一名合格的程序员,应该极力避免...

    前言

    在一些程序语言中,在某些情况下存在着一些未定义行为,其中以C和C++最为著名,在C++中,规定某些操作的行为是未定义的。这些未定义行为往往会导致一些程序的错误问题的出现,作为一名合格的程序员,应该极力避免这些未定义行为的出现,让程序的运行结果不会说出现不确定的情况。

    总结

    在C++中的常见的未定义行为主要有以下几种:

    数组越界问题

    在C++中,数组越界问题是一个重灾区,也是很多人容易疏忽的点。比如以下的程序

    const int ARR_SIZE = 10;
    int arr[ARR_SIZE];
    bool func(int i)
    {
        cout<<arr[i]<<endl;
        return true;
    }
    
    

    不对函数的入参进行校验,以判断其下标是否越界,就容易出现数组的越界问题,导致程序在某些情况下可能访问了非法内存进而导致程序崩溃。正确的做法,应该严格校验下标参数,防止出现数组越界问题。如下:

    const int ARR_SIZE = 10;
    int arr[ARR_SIZE];
    bool func(int i)
    {
        if(i >= 0 && i < ARR_SIZE)
        {
            cout<<arr[i]<<endl;
            return true;
        }
        return false;
    }
    
    

    非法多次释放同一块内存空间

    在C++中,提供了我们new跟delete的操作符来进行内存管理,这就是使得程序员有了很大的便利性,但是,对这两个操作符倘若使用不够恰当就可能会导致内存泄漏或者未定义行为。比如以下代码:

    bool func()
    {
        int *p = new int;
        delete p;
       	
        delete p;
    }
    

    在这里,由于程序员的疏忽,导致p指针指向的内存空间被释放了两次,这种非法操作也会导致程序未定义行为,导致程序出现不确定的运行结果。

    不恰当使用容器的erase操作

    在C++中,容器的出现确实给程序员带来了很多便利,但是如果操作不当便可能导致一些未定义行为,比如erase操作

    成员函数erase是从容器中指定位置删除元素。这无疑是个非常便利以及常用的成员函数,但是如果使用不恰当,便很容易出现程序出现错误的情况,一些粗心的程序员可能会写出下面的程序:

    bool func()
    {
        vector<int> vec = {1, 3, 4,5,9,5,6,7,5};
       	vector<int>::iterator it = vec.begin();
        for(; it != vec.end(); ++it)
        {
            if(*it == 5)
            {
                vec.erase(it);
            }
        }
        return true;
    }
    

    这里的原因便是erase函数在删除完元素,迭代器会失效,成为一个无效值,这样,再次执行 ++it 操作时便会导致未定义行为,因此,为了避免删除完元素的迭代器失效,导致程序崩溃,程序应该修改成这样:

    bool func()
    {
        vector<int> vec = {1, 3, 4, 5, 9, 5, 6, 7, 5};
       	vector<int>::iterator it = vec.begin();
        for(; it != vec.end();)
        {
            if(*it == 5)
            {
                vec.erase(it++);  //在C++11中,也可以这样写: it = vec.erase(it);
            }
            else
            {
                ++it;
            }
        }
        return true;
    }
    

    其他

    除了这些之外,在C++中还有很多其他未定义行为,比如说:

    • 除以0
    int res = 10 / 0;
    

    使用指针进行非法操作,如下:

    const int i = 10;
    int *p = (int*)&i;
    *p = 2;
    

    此时i被程序被声明为const的,即不能修改的,直接通过赋值对i进行修改并不能通过编译,但是通过指针,则能通过编译,i的值也由编译器决定。

    展开全文
  • 2016-08-03 09:48连伟杰 客户经理在我的整个职业生涯里我都在使用C++,而且现在C++依然是我做大多数项目时的首选编程语言。...如果用C,我将要么依赖第三方库要么不得不自己手动写一些自1970年来就早已存在的基...
  • C/C++未定义行为

    2021-05-21 04:59:59
    下面是一段代码,这段代码中有标准未定义行为。代码如下:#includeusing namespace std;int main(){int j = 0;for (int i = 0; i < 10; i++){j = (j++);}cout << j << endl;return 0;}编译器版本:...
  • C语言中的未定义行为

    2021-05-20 19:23:53
    C语言中的未定义行为(Undefined Behavior)是指C语言标准未做规定的行为。同时,标准也从没要求编译器判断未定义行为,所以这些行为有编译器自行处理,在不同的编译器可能会产生不同的结果,又或者如果程序调用未定义...
  • C语言中自增自减表达式的未定义行为.pdf
  • 在这个循环中,编译器可以假定该循环将迭代N+1次,如果“i”的溢出是未定义的,从而允许介入范围广泛的循环优化。另一方面,如果该变量被定义为溢出时回绕,那么编译器必须假设这个循环可能是无限的(当N是INT_MAX...
  • C/C++中未定义行为

    2017-08-29 10:20:25
    C/C++中未定义行为
  • 我在以下几行代码中重现了我在一个更大的项目中所经历的行为.我遗漏了#ifndef守卫和#include指令,试图提高可读性.调用make时会产生链接器错误. makefile包含在问题的末尾.C类继承自B的继承自A的O.O是完全不同的类....
  • 译自:http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html(可能需翻墙) ... 在第一部分中,我们对 C 中的未定义行为和它允许 C 较更“安全”之语言更为高效的几个情况略作了解。在第二部
  • 导致C++中产生未定义行为

    千次阅读 2019-03-12 21:24:48
    1、位运算: ...因此会产生未定义行为。 关于符号位如何处理C++标准没有明确规定,所以强烈建议将位运算用于处理无符号类型。因此,在使用位运算的过程中,因尽量将对象声明为无符号的,如:un...
  • 编译器(实现C / C ++标准)可以自由地做任何事情,因为CC ++标准未定义它们。 像Java这样的语言会立即发现错误,但是在少数情况下,像CC ++这样的语言会继续以静默但有错误的方式执行代码,这可能会导致无法...
  • 未定义行为

    2015-03-29 20:26:47
    C语言中的未定义行为(Undefined Behavior)是指C语言标准未做规定的行为。同时,标准也从没要求编译器判断未定义行为,所以这些行为有编译器自行处理,在不同的编译器可能会产生不同的结果,又或者如果程序调用...
  • Undefined behavior 是CC语言的一个方面,对于来自其他语言的程序员来说可能会令人惊讶(其他语言试图更好地隐藏它) . 基本上,即使许多C编译器不会报告程序中的任何错误,也可以编写不能以可预测的方式运行的C程序...
  • C标准为编译器提供了很大的优势来执行优化。 如果您假设一个简单的程序模型,其中未初始化的内存被设置为某个随机位模式,并且所有操作都按照它们的写入顺序执行,那么这些优化的后果可能会令人惊讶。注意:以下示例...
  • C语言中的未定义行为(Undefined Behavior)是指C语言标准未做规定的行为。同时,标准也从没要求编译器判断未定义行为,所以这些行为有编译器自行处理,在不同的编译器可能会产生不同的结果,又或者如果程序调用...
  • C++ 中的未定义行为

    2016-09-18 23:19:26
    如此众多的未定义行为,无疑给我们带来了许多麻烦,下面我们将列出一些常见的未定义行为,写程序时应该尽量避免。 指针相关的常见未定义行为有如下内容: 解引用 nullptr 指针; 解引用一个未初始化的指针; ...
  • 关于C代码中,不属于未定义行为的疑问,有没有人会的呢,请帮回答一下
  • 它有望在实践中运行,并且可以跨系统移植,即使没有任何标准规定.POSIX.1标准将其定义为未定义行为,不是因为它期望程序避免这种访问,而是因为定义安全访问情况会过于复杂并且可能限制将来的实现,因为有一个井很少甚至...
  • #2楼 该行为无法真正解释,因为它同时调用了未指定的行为未定义行为 ,因此我们无法对此代码做出任何一般性的预测,尽管如果您阅读Olve Maudal的著作(例如Deep C和Unspecified and Undefined),有时可以在...
  • C标准为编译器提供了很大的优势来执行优化 . 如果您假设一个简单的程序模型,其中未初始化的内存设置为某个随机位模式,并且所有操作都按照它们的写入顺序执行,那么这些优化的后果可能会令人惊讶 .注意:以下示例仅...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,306
精华内容 13,722
关键字:

c未定义行为