当前位置: 首页 > 知识库问答 >
问题:

C代码能在C 03和C 11中都有效,但做不同的事情吗?

冯风史
2023-03-14

C代码是否可能同时符合C 03标准和C 11标准,但根据编译标准的不同,可以做不同的事情?

共有3个答案

凌伟泽
2023-03-14

是的,有很多变化会导致相同的代码在C 03和C 11之间产生不同的行为。排序规则的差异导致了一些有趣的变化,包括一些以前未定义的行为变得定义良好。

1.初始化列表中同一变量的多个突变

一个非常有趣的角落情况是初始化器列表中同一变量的多个突变,例如:

int main()
{
    int count = 0 ;
    int arrInt[2] = { count++, count++ } ;

    return 0 ;
}

在C 03和C 11中,这都定义得很好,但在C 03中的计算顺序未指定,但在C 11中,它们是按照出现的顺序计算的。因此,如果我们在C 03模式下使用clang编译,它会提供以下警告(现场查看):

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

    int arrInt[2] = { count++, count++ } ;

                           ^        ~~

但在C11中没有提供警告(请看直播)。

二,。新的排序规则使i=i 1;在C 11中定义良好

C 03之后采用的新排序规则意味着:

int i = 0 ;
i = ++ i + 1;

在C 11中不再是未定义的行为,这将在缺陷报告637中介绍。排序规则和示例不一致

三,。新的排序规则也使我;在C 11中定义良好

C 03之后采用的新排序规则意味着:

int i = 0 ;
++++i ;

不再是C 11中未定义的行为。

4.稍微更明智的签名左移

C 11的后续草案包括N3485,我在下面链接它,修复了未定义的将1位移到或移过符号位的行为。这也包含在缺陷报告1457中。霍华德·希南特评论了这一变化的重要意义,即“左移”的思路(

五,。在C 11中,constepr函数可以被视为编译时常量表达式

C 11引入了constexpr函数,该函数:

constexr说明符声明可以在编译时评估函数或变量的值。然后,可以在只允许编译时常量表达式的情况下使用此类变量和函数。

虽然C 03没有constexpr功能,但我们不必显式使用constexpr关键字,因为标准库在C 11中提供了许多作为constexpr的函数。例如std::numeric_limits::min。这可能会导致不同的行为,例如:

#include <limits>

int main()
{
    int x[std::numeric_limits<unsigned int>::min()+2] ;
}

在C 03中使用clang这将导致x成为可变长度数组,这是一个扩展,并将生成以下警告:

warning: variable length arrays are a C99 feature [-Wvla-extension]
    int x[std::numeric_limits<unsigned int>::min()+2] ;
         ^

而在C 11std::numeric_limits中

六,。在C 11中,不会隐式地为析构函数生成异常规范

由于在C 11中,用户定义的析构函数具有隐含的noexcept(true)规范,如noexcept析构函数中所述,这意味着以下程序:

#include <iostream>
#include <stdexcept>

struct S
{
  ~S() { throw std::runtime_error(""); } // bad, but acceptable
};

int main()
{
  try { S s; }
  catch (...) {
    std::cerr << "exception occurred";
  } 
 std::cout << "success";
}

在C 11中,将调用std::terminate,但将在C 03中成功运行。

七,。在C 03中,模板参数不能有内部链接

这在std::sort不接受函数中声明的比较类的原因中得到了很好的介绍。因此,以下代码不应在C 03中工作:

#include <iostream>
#include <vector>
#include <algorithm>

class Comparators
{
public:
    bool operator()(int first, int second)
    {
        return first < second;
    }
};

int main()
{
    class ComparatorsInner : public Comparators{};

    std::vector<int> compares ;
    compares.push_back(20) ;
    compares.push_back(10) ;
    compares.push_back(30) ;

    ComparatorsInner comparatorInner;
    std::sort(compares.begin(), compares.end(), comparatorInner);

    std::vector<int>::iterator it;
    for(it = compares.begin(); it != compares.end(); ++it)
    {
        std::cout << (*it) << std::endl;
    }
}

但是目前clang允许在C 03模式下使用此代码并带有警告,除非您使用-迂腐-错误标志,这有点令人讨厌,请现场查看。

8.

使用

#include <iostream>
template<int I> struct X {
  static int const c = 2;
};
template<> struct X<0> {
  typedef int c;
};
template<typename T> struct Y {
  static int const c = 3;
};
static int const c = 4;
int main() {
  std::cout << (Y<X<1> >::c >::c>::c) << '\n';
  std::cout << (Y<X< 1>>::c >::c>::c) << '\n';
}

C 03中的结果是:

0
3

在C 11中:

0
0

九,。C 11改变了一些std::vector构造函数

从这个答案稍微修改的代码表明,使用以下构造函数从std::向量:

std::vector<T> test(1);

在C 03和C 11中产生不同的结果:

#include <iostream>
#include <vector>

struct T
{
    bool flag;
    T() : flag(false) {}
    T(const T&) : flag(true) {}
};


int main()
{
    std::vector<T> test(1);
    bool is_cpp11 = !test[0].flag;

    std::cout << is_cpp11 << std::endl ;
}

十,。缩小聚合初始值设定项中的转换范围

在C 11中,聚合初始值设定项中的缩小转换格式不正确,看起来gcc在C 11中和C 03中都允许这样做,尽管它在C 11中默认提供了一个警告:

int x[] = { 2.0 };

这在草案C 11标准章节8.5.4列表初始化第3段中进行了说明:

类型为T的对象或引用的列表初始化定义如下:

并包含以下项目符号(我的重点):

否则,如果T是类类型,则会考虑构造函数。列举了适用的构造函数,并通过重载解析(13.3、13.3.1.7)选择了最佳构造函数。如果转换任何参数都需要缩小转换(见下文),则程序的格式不正确

这一点以及更多的例子在草案C标准章节附录C.2C和ISO C 2003中进行了介绍。它还包括:

>

  • 新类型的字符串文字[…]具体来说,当与字符串文字相邻时,名为R、u8、u8R、u、uR、u、uR或LR的宏不会展开,而是会被解释为字符串文字的一部分。例如

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
    

    用户定义的文字字符串支持[…]之前,#1将由两个单独的预处理标记组成,宏x将被扩展。在该国际标准中,#1由单个预处理标记组成,因此宏不展开。

    #define _x "there"
    "hello"_x // #1
    

    指定整数/和%[...] 2003代码的结果的舍入,该代码使用整数除法将结果舍入为0或负无穷大,而本国际标准始终将结果舍入为0。

    size()成员函数的复杂性现在保持不变[…]一些符合C 2003的容器实现可能不符合本国际标准中指定的size()要求。根据更严格的要求调整容器(如std::list)可能需要进行不兼容的更改。

    更改std::ios_base::failure的基类[…]std::ios_base::failure不再直接派生自std::exception,而是现在派生自std::system_error,后者又派生自std::runtime_error。假定std::ios_base::failure直接来自std::exception的有效C 2003代码在本国际标准中的执行方式可能不同。

  • 仲孙文乐
    2023-03-14

    我向您介绍这篇文章和后续文章,其中有一个很好的例子说明了

    bool const one = true;
    int const two = 2;
    int const three = 3;
    
    template<int> struct fun {
        typedef int two;
    };
    
    template<class T> struct fon {
        static int const three = ::three;
        static bool const one = ::one;
    };
    
    int main(void) {
        fon< fun< 1 >>::three >::two >::one; // valid for both  
    }
    

    关键部分是main中的行,它是一个表达式。

    1 >> ::three = 0
    => fon< fun< 0 >::two >::one;
    
    fun< 0 >::two = int
    => fon< int >::one
    
    fon< int >::one = true
    => true
    
    fun< 1 > is a type argument to fon
    fon< fun<1> >::three = 3
    => 3 > ::two > ::one
    
    ::two is 2 and ::one is 1
    => 3 > 2 > 1
    => (3 > 2) > 1
    => true > 1
    => 1 > 1
    => false
    

    恭喜,同一个表达式有两个不同的结果。当然,当我测试C 03时,它确实发出了一个警告。

    戈正初
    2023-03-14

    答案是肯定的。好的一面是:

    • 以前隐式复制对象的代码现在将尽可能隐式移动它们

    在负面方面,本标准附录C中列出了几个示例。尽管消极因素比积极因素多得多,但每一种情况都不太可能发生。

    字符串常量

    #define u8 "abc"
    const char* s = u8"def"; // Previously "abcdef", now "def"
    

    #define _x "there"
    "hello "_x // Previously "hello there", now a user defined string literal
    

    0的类型转换

    在C 11中,只有文字是整数空指针常量:

    void f(void *); // #1
    void f(...); // #2
    template<int N> void g() {
        f(0*N); // Calls #2; used to call #1
    }
    

    整数除法和取模后的舍入结果

    在C 03中,编译器被允许向0或负无穷大进位。在C 11中,必须向0进位

    int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0
    

    嵌套模板关闭大括号之间的空白

    在专业化或实例化中,

    template< unsigned len > unsigned int fun(unsigned int x);
    typedef unsigned int (*fun_t)(unsigned int);
    template< fun_t f > unsigned int fon(unsigned int x);
    
    void total(void) {
        // fon<fun<9> >(1) >> 2 in both standards
        unsigned int A = fon< fun< 9 > >(1) >>(2);
        // fon<fun<4> >(2) in C++03
        // Compile time error in C++11
        unsigned int B = fon< fun< 9 >>(1) > >(2);
    }
    

    操作员new现在可以抛出除std::bad_alloc

    struct foo { void *operator new(size_t x){ throw std::exception(); } }
    try {
        foo *f = new foo();
    } catch (std::bad_alloc &) {
        // c++03 code
    } catch (std::exception &) {
        // c++++11 code
    }
    

    用户声明的析构函数有一个隐式异常规范示例,来自于C 11中引入的破坏性更改?

    struct A {
        ~A() { throw "foo"; } // Calls std::terminate in C++11
    };
    //...
    try { 
        A a; 
    } catch(...) { 
        // C++03 will catch the exception
    } 
    

    容器的size()现在需要在O(1)中运行

    std::list<double> list;
    // ...
    size_t s = list.size(); // Might be an O(n) operation in C++03
    

    std::ios_base::failure不再直接派生自std::except

    虽然直接基类是新的,但std::runtime_error不是。因此:

    try {
        std::cin >> variable; // exceptions enabled, and error here
    } catch(std::runtime_error &) {
        std::cerr << "C++11\n";
    } catch(std::ios_base::failure &) {
        std::cerr << "Pre-C++11\n";
    }
    

     类似资料:
    • 在任何情况下,一段在C和C++中都有效的代码在用每种语言的标准编译器编译时会产生不同的行为吗? 为了使它成为一个合理/有用的比较(我试图学习一些实际有用的东西,而不是试图在问题中发现明显的漏洞),让我们假设: 没有与预处理器相关的内容(这意味着没有对、pragmas等进行攻击) 实现定义的任何内容在两种语言中都是相同的(例如,数字限制等) 我们正在比较每个标准的最新版本(例如,C++98和C90或

    • 我想用C#做一个简单的HTTP请求,但是有些东西不工作,我得到的是< code>403 Forbidden状态代码。 当我尝试在邮递员中执行相同的请求时,一切正常。我尝试运行 Fiddler 并查看邮递员发送的所有标头。我复制粘贴了所有这些,但我仍然在 C# 代码发送的请求中得到了 。 有人能解释一下为什么这不起作用吗?同样的标题,同样的一切。 我用“example.com”替换了 url,因为我

    • 问题内容: 当我使用pip时,没有sudo通常不会起作用。我经常看到人们在不使用sudo的情况下使用pip,所以我在做什么错呢? 我读到不建议使用sudo安装pip软件包。我知道可以不用sudo使用pip,但是要安装,我必须先使用sudo。 当我尝试不使用sudo安装pip时,我得到: 尝试通过以下方式安装flask时: 问题答案: 在Unix / Linux系统中用于在另一个用户中使用 其 权限

    • 问题内容: 大多数Java代码也是语法上有效的Groovy代码。但是,有一些例外使我想到了这个问题: 在Groovy中,Java中哪些构造/功能在语法上无效? 请提供无效的Groovy代码(Groovy 1.6)的Java代码(Java 1.6)的具体示例。 更新: 到目前为止,我们有五个语法上有效的Java代码示例,它们不是有效的Groovy代码: 数组初始化 内部班 是Groovy中的关键字,

    • 下面的代码是登录系统。用户名是管理员,密码是密码(您可以稍后更改),它现在做的是在表单发送后,它只是显示登录页面。我找不到它有什么问题。请帮忙。error_log文件没有显示任何内容。可能是密码散列不正确。当我设置密码时 然后插入结果。 编辑:现在我收到以下错误:[2015年11月15日07:00:58欧洲/莫斯科]PHP警告:mysqli_connect():(28000/1045):用户'la

    • 问题内容: 我对Web编程的经验不是很丰富,并且实际上还没有在Node.js中编写任何代码,只是对事件驱动的方法感到好奇。看起来确实不错。 这篇文章解释了当我们使用基于线程的方法来处理请求时可能发生的一些不好的事情,而应该选择事件驱动的方法。在基于线程的情况下,收银员/线程会一直困扰我们,直到我们准备好食物/资源为止。在事件驱动下,收银员将我们发送到请求队列之外的某个地方,因此我们在等待食物时不会