免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: windyrobin
打印 上一主题 下一主题

C++ 这孩子 是不是误入歧途了? [复制链接]

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
51 [报告]
发表于 2011-11-30 12:19 |只看该作者
回复 49# zylthinking

>> 用错误代码, 本身就在上下文里面, 至少第一现场在上下文里面, 处理或不处理取决于程序员想干什么, 而 try catch 一旦也想这么干, 那么就失去了随意向上冒泡的自由

能不能换个角度, 这么说……
用异常, 如果想在第一现场的上下文里面处理, 就try catch; 想在第几现场处理, 就在第几现场try catch; 否则就让异常往上冒 —— 这才叫自由好不好……
而用错误代码, 无论你是否想在这个现场处理, 都必须 failure = f(arg...); if (failture) ...  没有自动往上报告的自由。

论坛徽章:
1
2015年迎新春徽章
日期:2015-03-04 09:49:45
52 [报告]
发表于 2011-11-30 12:21 |只看该作者
这个是程序业务逻辑啊, 该怎么处理合适怎么处理, 但离开这个上下文后, 一旦丢失信息, 你还真不知道 ...
zylthinking 发表于 2011-11-30 12:10

异常可以携带部分信息到上层函数去的。另外如果用错误码的话,绝大部分情况下,子函数里也就是遇到错误后也就是打个日子、清理一下、然后往上返回,因为子函数里缺少全局信息~~~

错误处理本来就是个麻烦问题,没有完美的解决方案的。

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
53 [报告]
发表于 2011-11-30 12:23 |只看该作者
回复  zylthinking

>> 这个是程序业务逻辑啊, 该怎么处理合适怎么处理, 但离开这个上下文后, 一旦丢 ...
OwnWaterloo 发表于 2011-11-30 12:14


又开始较真了, 好吧, 我就逐层退出, 或者尝试另一个手段, 比如 malloc() 失败, 直接使用提前申请的一段用于应急的内存, 怎么不可以啊。
不再于如何处理, 而在于仍然在这个上下文中, 而异常一旦冒泡退出好几层, 只有2种可能, 1, 中间不做catch 没关系, 做catch 的地方是正确合适的地方, 但其实这个和错误码逐层退出一样, 所谓catch的地方正是检查error code 做处理的地方 2, 不管三七二十一, 一直退出到main, 那就完蛋了, mian 只能输出 xxx错误, 然后 exit, 这个自然也是程序员的责任, 因为他没有在合适的地方catch, 但一旦在合适的地方catch了, 那干嘛不在那个地方 if 呢, throw 比 goto 也没少写一行代码; 而且, 异常不是说自己集中处理错误么, 那干嘛不在mian 里面处理?

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
54 [报告]
发表于 2011-11-30 12:24 |只看该作者
算了, 这个话题本来谁也说服不了谁, 休战吧

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
55 [报告]
发表于 2011-11-30 12:33 |只看该作者
回复 53# zylthinking

>> 又开始较真了, 好吧, 我就逐层退出, 或者尝试另一个手段, 比如 malloc() 失败, 直接使用提前申请的一段用于应急的内存, 怎么不可以啊。
不是较真。  异常可以根据是否需要处理来 try catch, 而错误代码无论是否能处理都要 if 检测, 怎么都不能算异常没有自由吧……

逐层退出? 意思是逐层都要检测分配失败这种错误是吧?
好的, 如果用异常, bad_alloc 让它往上冒, main 里面记录一下, 中间不需要任何层次去关心内存分配失败这个事情。

直接使用应急内存? 好的, 这就是 set_newhandler 的用场。


>> 不再于如何处理, 而在于仍然在这个上下文中, 而异常一旦冒泡退出好几层, 只有2种可能, 1, 中间不做catch 没关系, 做catch 的地方是正确合适的地方, 但其实这个和错误码逐层退出一样, 所谓catch的地方正是检查error code 做处理的地方

怎么与逐层退出是一样呢…… 逐层退出每个层次都要 if 好吗……


>> 2, 不管三七二十一, 一直退出到main, 那就完蛋了, mian 只能输出 xxx错误, 然后 exit, 这个自然也是程序员的责任, 因为他没有在合适的地方catch, 但一旦在合适的地方catch了, 那干嘛不在那个地方 if 呢, throw 比 goto 也没少写一行代码; 而且, 异常不是说自己集中处理错误么, 那干嘛不在mian 里面处理?
不仅仅需要在合适的地方写 if , 而是需要在所有地方写 if ……  如果不打算忽略什么错误的话 <- 这是前提, 没错吧?

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
56 [报告]
发表于 2011-11-30 12:36 |只看该作者
回复  zylthinking

>> 用错误代码, 本身就在上下文里面, 至少第一现场在上下文里面, 处理或不处理取 ...
OwnWaterloo 发表于 2011-11-30 12:19


再多说一句吧, 确实是没有自动上报的自由, 但考虑如下代码

function(){
A a;
a.open(file);
try{

}catch(...){
    throw again;
}
   
a.close();
}
在破坏函数里面, 我就是不调用 close, 现在异常来了, 你 a要销毁吧, 那么文件句柄泄露了没有?
自动的同时也增加了不可控性, 而且, 层层处理本身是软件逻辑分层的原则吧, 随意冒泡, 至少是挑战了这个原则的

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
57 [报告]
发表于 2011-11-30 12:43 |只看该作者
回复 56# zylthinking

>> 在破坏函数里面, 我就是不调用 close, 现在异常来了, 你 a要销毁吧, 那么文件句柄泄露了没有?

如果A是fstream什么的, 那就没有泄露, a的销毁就会考虑文件是否打开, 如果是就关闭。
这就是所谓的异常安全……


>> 自动的同时也增加了不可控性, 而且, 层层处理本身是软件逻辑分层的原则吧, 随意冒泡, 至少是挑战了这个原则的
就是在挑战层层处理这种做法。  而且不止C++, 随便什么语言都在挑战…… 连C都有setjmp/longjmp呢……
至于这个是不是原则嘛……



我感觉吧……  你是以错误代码的方式在使用异常……  不层层抓就不放心……
你可以尝试一下……  先将代码写成异常安全(主要是靠析构函数), 然后放心大胆的别去抓……
只有当 catch 里面真的能写点什么东西的时候才去抓……
看会不会有新的体会……

论坛徽章:
0
58 [报告]
发表于 2011-11-30 15:21 |只看该作者
本帖最后由 sonicling 于 2011-11-30 15:22 编辑
回复  sonicling

我想做C的语法分析……
因为感觉那些传统的ctags,cscope工具弱爆了……

弱爆了有些夸张……  要用也能用, 但就是各种蛋疼……
不过实力不够……  而且又很懒……  一直没能动手……

C++语法分析更是想都不敢想……
OwnWaterloo 发表于 2011-11-30 03:11



    代码编辑器这类工具的语法分析跟编译器的还不一样,我觉得比编译器的语法分析不一样。这类编辑器要即时分析,考虑到效率问题,不大可能每次输入的时候进行全文分析,而只能局部分析,所以要对文件进行动态分割、并对文法进行划分,对每个文件部分要选择正确的那部分文法进行分析,这是复杂的地方。简化的地方是编辑器不需要考虑语法树之类的全局结构,它只用标出每个token的属性就行了。

我之前考虑C/C++的语法分析使用自顶向下的分析,但是回溯太麻烦了,要不想回溯,就得文法变形,写了个自动化变形工具,也很麻烦。所以后来改用LR1,就不用回溯了,但是C/C++即便是用LR分析也存在二义性问题,最头疼的就是当你读到一个identifier时,基本上会遇到归约冲突,比如class-name、enum-name、typedef-name、unqualified-id、declarator-id、id-expression等等。然后又祭出文法变形的办法,还是没办法解决。

根本问题是:C/C++的文法根本就不是Context-Free的,最典型的:一个identifier在‘(’前到底如何归约依赖于之前的分析的语义属性。如果它之前声明为class、struct、union,那么就归约成class-name,如果之前声明为enum就是enum-name,如果被typedef,那就是typdef-name,否则就是unqualified-id。而一个unqualified-id在‘(’前也有归约冲突,如果是声明为函数或变量的,那就是id-expression,否则就是declarator-id。

我的解决办法是用lr动作表+pda构成一个generic_parser,这个generic_parser提供一个hint的virtual方法。当lr动作表中出现归约冲突的时候,把所有候选项和当前的归约栈传给hint,由它来决定如何归约。然后我做了一个cpp_parser继承自这个generic_parser,cpp_parser包含符号表和这个hint的实现(具体是parser把hint转交给另外一个独立的static实现)。
  1. const lr_action & cpp_node_info::hint( cpp_parser *parser, const std::vector<cpp_ast_node *> value_stack, set_action_t::const_iterator &first, set_action_t::const_iterator &last )
  2. {
  3.         static const parser_string unqualified_id                       = _T("unqualified-id");
  4.         static const parser_string mem_initializer_id                 = _T("mem-initializer-id");
  5.         static const parser_string class_name                           = _T("class-name");
  6.         static const parser_string original_namespace_name   = _T("original-namespace-name");
  7.         static const parser_string namespace_alias                  = _T("namespace-alias");
  8.         static const parser_string class_or_namespace_name  = _T("class-or-namespace-name");
  9.         static const parser_string declarator_id                         = _T("declarator-id");
  10.         static const parser_string simple_type_specifier            = _T("simple-type-specifier");
  11.         static const parser_string primary_expression               = _T("primary-expression");
  12.         static const parser_string ptr_operator                         = _T("ptr-operator");
  13.         static const parser_string unary_operator                     = _T("unary-operator");
  14.         static const parser_string template_name                     = _T("template-name");
  15.         static const parser_string elaborated_type_specifier    = _T("elaborated-type-specifier");
  16.         /*
  17.         requisitions:
  18.         1. all actions are reductions
  19.         2. all productions are the same in length.
  20.         */
  21.         const int len = first->second.prod_length; // reduction length
  22.         std::map<parser_string, const lr_action *> candidiates;
  23.         for (set_action_t::const_iterator it = first; it != last; ++it)
  24.         {
  25.                 const lr_action &action = it->second;
  26.                 assert(action.action == lr_action::reduct && len == action.prod_length);
  27.                 candidiates.insert(std::make_pair(parser_string(action.prod_name), &action));
  28.         }

  29.         if (len == 1 && !value_stack.empty())
  30.         {
  31.                 const cpp_ast_node *node = value_stack.back();
  32.                 assert(node != NULL);
  33.                 const cpp_node_info &info = node->_info;
  34.                 /*
  35.                 if info.lang_element is a symbol, it would be *-id (unqualified-id, mem-initializer-id)
  36.                 if info.lang_element is a type, it would be *-name (class-name, original-namespace-name, namespace-alias, typedef-name)
  37.                 if info.lang_element is a template, it would be template-name
  38.                 if it is not matched, result will be guessed in following order :
  39.                 unqualified-id
  40.                 mem-initializer-id
  41.                 original-namespace-name
  42.                 class-name
  43.                 enum-name
  44.                 namespace-alias
  45.                 typedef-name
  46.                 template-name(<)

  47.                 the following pairs of collision will resolved in default:

  48.                 class-or-namespace-name
  49.                 type-name

  50.                 simple-type-specifier
  51.                 declarator-id

  52.                 declarator-id
  53.                 primary-expression

  54.                 ptr-operator
  55.                 unary-operator
  56.                 */

  57.                 // name lookup
  58.                 language_element *element = NULL;
  59.                 if (node->get_child_count() == 0)
  60.                 {
  61.                         symbol_scope *current = parser->get_current_scope();
  62.                         if (current)
  63.                         {
  64.                                 element = current->find_symbol_inherited(node->get_ast_name());
  65.                         }
  66.                 }
  67.                 else
  68.                 {
  69.                         element = info.lang_element;
  70.                 }
  71.                 std::map<parser_string, const lr_action *>::const_iterator result;
  72.                 // check type of name
  73. #define try_return(name) \
  74.                 if ((result = candidiates.find(name)) != candidiates.end()) return *(result->second)

  75.                 if (QUERYID(language_symbol, element) != 0)
  76.                 {
  77.                         try_return(unqualified_id);
  78.                         try_return(mem_initializer_id);
  79.                         try_return(primary_expression);  // try primary_expression first
  80.                         try_return(declarator_id);
  81.                 }
  82.                 if (QUERYID(composite_type, element) != 0)
  83.                 {
  84.                         try_return(class_name);
  85.                         try_return(simple_type_specifier);
  86.                 }
  87.                 if (QUERYID(namespace_manager, element) != 0)
  88.                 {
  89.                         try_return(original_namespace_name);
  90.                 }
  91.                 if (QUERYID(language_template, element) != 0)
  92.                 {
  93.                         try_return(template_name);
  94.                 }
  95.                 try_return(unqualified_id);
  96.                 try_return(mem_initializer_id);
  97.                 try_return(primary_expression);

  98.                 goto default_hint;
  99.         }
  100.         else if (len <= 3 && !value_stack.empty())
  101.         {
  102.                 std::map<parser_string, const lr_action *>::const_iterator result;
  103.                 /*
  104.                 elaborated-type-specifier
  105.                 class-head
  106.                 */
  107.                 try_return(elaborated_type_specifier);
  108.                 assert(false);
  109.         }
  110.         else
  111.         {
  112.                 assert(false);
  113.         }
  114. default_hint:
  115.         static const parser_string seq[] = {unqualified_id, mem_initializer_id, class_name, original_namespace_name, namespace_alias, simple_type_specifier, primary_expression, declarator_id, elaborated_type_specifier};
  116.         for (size_t i=0; i<countof(seq); i++)
  117.         {
  118.                 std::map<parser_string, const lr_action *>::const_iterator result = candidiates.find(seq[i]);
  119.                 if (result != candidiates.end())
  120.                         return *(result->second);
  121.         }
  122.         return first->second;
  123. }
复制代码
函数前面声明的那一大堆static const parser_string是所有可能冲突的非终结符。由lr动作表的生成程序决定的。生成程序会产生一个归约报告,表示这些非终结符会发生归约冲突,并显示这些归约的归约长度。

至此就基本解决了C++所有的归约冲突,C++03的文法也无需做任何变形,直接从标准中copy过来就可以用。

论坛徽章:
0
59 [报告]
发表于 2011-11-30 15:51 |只看该作者
我准备拜你为滑铁卢大神,你就是俺心中的神啊。

压片机

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
60 [报告]
发表于 2011-11-30 17:41 |只看该作者
本帖最后由 OwnWaterloo 于 2011-11-30 17:44 编辑
    代码编辑器这类工具的语法分析跟编译器的还不一样,我觉得比编译器的语法分析不一样。这类编辑器要即时分析,考虑到效率问题,不大可能每次输入的时候进行全文分析,而只能局部分析,所以要对文件进行动态分割、并对文法进行划分,对每个文件部分要选择正确的那部分文法进行分析,这是复杂的地方。简化的地方是编辑器不需要考虑语法树之类的全局结构,它只用标出每个token的属性就行了。
sonicling 发表于 2011-11-30 15:21


嗯, 确实如此。

但我是想让分析尽可能准确的, 而不是又弄出一个fake工具……

比如要分析宏展开后的代码, emacs里面到处都是类似DEFINE_FUNCTION(name,....) 这样的东西, 展开后其实类似与 return_type ???_name(param....)
etags 默认有识别这种模式的正则……
ctags, cscope 只能哭了……

肯定要能处理宏定义, 配合编辑器, 就可以在编辑器里将某个宏展开, 而且较好的排版。
类似于将宏当作一个可折叠的东西。

比如分析该文件的依赖, 类似 gcc -MM。
都这个年代了, C/C++ 编译个东西还这么蛋疼……
依赖关系本身就可以从源代码里获得, 但C/C++不要求编译器做这个……   上世纪的思想……  一点都不为程序员着想……

比如分析变量名引用的是哪个变量。

  1. extern int x;  /* #0 */
  2. int f(void)
  3. {
  4.       x = 0;
  5.       ...
  6.       {
  7.             int x; /* #1 */
  8.             ...
  9.             x = 1; /* 1 */
  10.             ...
  11.       }
  12.       x = 2 /* 0 */
  13.       ...
  14. }
复制代码
先不说全局(或者说静态)变量是否应该避免……
假设有一堆源代码, 某个文件有上面的f, 很明显它改动了全局x。
如果想知道这个全局x还在其他什么地方被改动了 —— 这东西是可以通过分析源文件知道的。
但现有的一些工具很可能就会去找类似 /* 1 */ 那个地方。

比如语意diff。
比较行的diff也是那种凑合着不用也得用的工具……
比如上面的f函数, 一行代码不改, 只是移动一个位置, diff 就傻了……  增加一坨,减少一坨, 而不是移动一坨。

比如语意的apply。
比如修改了一个头文件里的结构体, 将名字从T改为U。
能够将这个改动应用到包含这个头文件的源文件中, 将原来引用T的地方改为引用U。
等等等等……



你后面那段代码我就完全看不明白了……
我是想先在lisp上试试刀, 毕竟这东西分析容易得多……
而且要做编辑器前端的话, 比如emacs, 也会写很多lisp代码, 正好用来验证。
这方面完全没经验啊……
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP