C++ 中捕获整数除零错误
继承自 C 的优良传统, C++ 也是一门非常靠近底层的语言, 可是实在是太靠近了, 很多问题语言本身没有提供解决方案, 可执行代码贴近机器, 运行时没有虚拟机来反馈错误, 跑着跑着就毫无征兆地崩溃了, 简直比过山车还刺激. 虽然 C++ 加入了异常机制来处理很多运行时错误, 但是异常机制的功效非常受限, 很多错误还没办法用原生异常手段捕捉, 比如整数除 0 错误. 下面这段代码 #include <iostream>
int main() { try { int x, y; std::cin >> x >> y; std::cout << x / y << std::endl; } catch (...) { std::cerr << "attempt to divide integer by 0." << std::endl; } return 0; }
输入 "1 0" 则会导致程序挂掉, 而那对 try-catch 还呆在那里好像什么事情都没发生一样. 像 Python 一类有虚拟机环境支持的语言, 都会毫无悬念地捕获除 0 错误. 使用信号 不过, 底层自然有底层的办法. 这得益于硬件体系中的中断机制. 简而言之, 当发生整数除 0 之类的错误时, 硬件会触发中断, 这时操作系统会根据上下文查出是哪个进程不给力了, 然后给这个进程发出一个信号. 某些时候也可以手动给进程发信号, 比如恼怒的用户发现某个程序卡死的时候果断 kill 掉这个进程, 这也是信号的一种. 这次就不是 C 标准了, 而是 POSIX 标准. 它规定了哪些信号进程不处理也不会有太大问题, 有些信号进程想处理也是不行的, 还有一些信号是错误中断, 如果程序处理了它们, 那么程序能继续执行, 否则直接杀掉. 不过, 这些错误处理默认过程都是不存在的, 需要通过调用 signal 函数配置. 方法类似下面这个例子 #include <csignal> #include <cstdlib> #include <iostream>
void handle_div_0(int) { std::cerr << "attempt to divide integer by 0." << std::endl; exit(1); }
int main() { if (SIG_ERR == signal(SIGFPE, handle_div_0)) { std::cerr << "fail to setup handler." << std::endl; return 1; } int x, y; std::cin >> x >> y; std::cout << x / y << std::endl; return 0; }
可以看出, signal 接受两个参数, 分别是信号编号和信号处理函数. 成功设置了针对 SIGFPE (吐槽: 为什么是浮点异常 FPE 呢?) 的处理函数 handle_div_0 , 如果再发生整数除 0 的惨剧, handle_div_0 就会被调用. handle_div_0 的参数是信号码, 也就是 SIGFPE , 忽略它也行. 底层机制
Posted at Jul 24 2010 - 13:17:37
Permanent Link:
/p/100
Load full text
|
Post tags:
POSIX
C
Exception Handling
Signal
C++
|
char** 不能赋值给 char const**
C 和 C++ 允许把指向 "某物" 的指针赋值给指向 "不可改变" 的 "某物" 的指针, 如 char* call = "fsck"; char const* call_const = call;
而且, 如果确定不会更改所知向的对象, 那么推荐加上 const , 这是 C++ 的代码编写建议之一. 但是, 要把指向 “某物” 的二级指针赋值给... 见鬼, 简单的说, 下面这种赋值, 任何一个负责任的 C++ 编译器会报出指针不兼容错误: char* call = "fsck"; char** ptr_to_call = &call; char const** ptr_const_to_call = ptr_to_call; // error
C/C++ 这样做, 目的其实是在于防范将 char const* 赋值给 char* 的意外行为. 这样说也许很难理解, 那么请看下面这段示例代码 Code Snippet 0-0
char const* CALL = "fsck";
void bind(char const** m) { *m = CALL; }
int main(void) { char* s; bind((char const**)&s); // explicit convert s[1] = 'u'; // crash on write return 0; }
在调用 bind 时, 采取强制转换来规避编译错误. 而此时调用 bind 就出现了问题, 它把 char const* 类型的 CALL 变量赋值给了实际上是 char* 的 s , 结果就是在紧接下来的那个赋值中, 程序由于尝试去写只读内存区而崩溃. 有崩溃有真相, 这就是对标题的解答. 另一个类似的例子是, 子类对象的二级指针不能赋值给父类对象的二级指针, 如 Code Snippet 0-1
struct base {}; struct inherit : base {};
void f(void) { inherit** i = NULL; base** b = i; // error }
Posted at Apr 25 2010 - 05:12:33
Permanent Link:
/p/83
Load full text
|
Post tags:
C
C++
const
|
Makefile 与 C 语系头文件依赖自动解析
h1. 如何获得源文件依赖的头文件? 这一部分内容与 Makefile 毫无关系, 它由编译器完成. 以 g++ 为例, 可以尝试使用以下方法输出 .cpp 文件依赖的头文件 g++ -M [your cpp file]
乱糟糟的, 不是吗? 这是因为 g++ 把依赖的库文件也给列出来了. 忽略库文件, 则使用这样的命令 g++ -MM [your cpp file]
显然, 这些东西扔到标准输出是毫无用处的, 可以对其进行重定向 g++ -MM [your cpp file] > [some file]
但是这还是有问题, 只把依赖关系放进文件当然不行, 编译源文件的指令还没弄出来呢. 下面就得另外想办法了. h1. 怎么添加编译指令并运行它们? 添加指令本质上就是追加文件内容, 方法有难有简单, 最简单的无非是写个脚本 echo 并追加重定向到文件中. 在这篇粗陋的文章里就用这招了. 好, 写个 Makefile 脚本 [target.o]:[your cpp file] ....g++ -MM $< > Makefile.tmp ....echo "....g++ $< -c" >> Makefile.tmp ....make -f Makefile.tmp
这里的四个点号 (....) 表示一个制表符, 嗯, 是的, Makefile 只能用万恶的制表符来缩进, 而且还必须缩进; Makefile.tmp 一定不要是当前的 Makefile, 要不然就出现文件覆盖的悲剧了... 结果就是, 一旦 make 到这个目标, 它会生成一串依赖关系和构建方法到另一个 Makefile, 然后把编译这等脏活累活都甩给那个倒霉的家伙. h1. 怎么转移目标? 如果上述目标仍然写成 xxx.o : xxx.cpp , 那么如果仅仅是依赖的头文件被修改了, 这个目标仍然不会被 make 到. 一个很挫的方法是, 不要把目标定为 xxx.o 文件, 相反, 弄成一个永不会生成的目标比较好, 比如叫 xxx.d 这个名字. 下面上一个完整的例子. 这个例子中有个叫做 DEP 的 Makefile 变量没有被声明过, 嗯, 这将是你的事情. 请将所有需要编译并连接的 cpp 源文件列个表, 然后修改它们的后缀变成 d, 那么 DEP 的值就是这个表. Code Snippet 0-0
CXX=g++ CXXFLAGS=-Wall -pg MKTMP=Makefile.tmp
all:object
object:$(DEP) ....$(CXX) $(CXXFLAGS) *.o -o $@
%.d:%.cpp ....$(CXX) -MM $< > $(MKTMP) ....echo "....$(CXX) $< $(CXXFLAGS) -c" >> $(MKTMP) ....make -f $(MKTMP)
clean: ....rm -f *.o ....rm -f test.out ....rm -f $(MKTMP)
再次提醒, 制表符出没注意. (可利用编辑器的全文替换, 4 点换成 1 制表符) 假如现在目录下有 main.cpp, class0.cpp, class1.cpp, 那么定义 DEP=main.d class0.d class1.d
将这句话插进上述源码中的开头位置然后 make 即可. h1. 怎么收拾掉乱七八糟的输出? 接脏活的临时工经常会反馈出一些无聊的信息, 比如进入这个离开那个的, 如果希望保持控制台的清洁, 那么把不想看的都扔进垃圾桶吧 make > /dev/null
不要担心看不到 warning 和 error, 它们是通过 stderr 输出的, 而上述重定向只会干扰 stdout.
Posted at Dec 07 2009 - 11:34:35
Permanent Link:
/p/63
Load full text
|
Post tags:
C
C++
Makefile
|
已知两圆圆心坐标及半径求两圆交点
在一个二维平面上给定两个圆的圆心横纵坐标、半径共 6 个参数, 求交点. 即实现下面的 C 函数 int intersect(struct circle_t const circles[], struct point_t intersections[]);
其中 point_t 与 circle_t 的定义分别是 Code Snippet 0-0
struct point_t { double x; double y; };
struct circle_t { point_t center; double r; };
函数 intersect 的输入参数为两个圆, 返回交点个数, 另外, 交点详细信息将被存入另一个参数数组 intersections 中. 由于两个圆之多有 2 个交点, 因此该函数可以如下形式调用: Code Snippet 0-1
#include <stdio.h>
int main(void) { struct circle_t circles[2]; struct point_t points[2];
/* 从 stdin 输入圆参数 * 按照如下格式 * $(x_0, y_0, r_0)$ * $(x_1, y_1, r_1)$ */ scanf("%lf%lf%lf%lf%lf%lf", &circles[0].center.x, &circles[0].center.y, &circles[0].r, &circles[1].center.x, &circles[1].center.y, &circles[1].r);
// 如果两个圆相同 // *注意:* 由于 x, y, r 都是浮点数, 使用 == 进行判同可能导致问题 // 作为示例代码还是姑且这么写了 if (circles[0].center.x == circles[1].center.x && circles[0].center.y == circles[1].center.y && circles[0].r == circles[1].r) { puts("The circles are the same."); return 0; }
switch (*intersect(circles, points)*) { case 0: puts("No intersection."); break; case 1: printf("(%.3lf %.3lf)n", points[0].x, points[0].y); break; case 2: printf("(%.3lf %.3lf) (%.3lf %.3lf)n", points[0].x, points[0].y, points[1].x, points[1].y); } return 0; }
用程序实现求交点方法可以有多种, 比较极端的甚至可以用到二分逼近, 反正计算机的计算能力跟笔算是两个世界; 不过本文还是老实一点, 仍试着来解方程. 在求解过程中, 除了用到圆的标准方程 $ (x - x_0)^2 + (y - y_0)^2 = r_0^2 (其中 $(x_0, y_0)$ 是其中一个圆的圆心, $r_0$ 是其半径; 当然对另一圆也是如此, 此处省略之) 圆的参数方程也将会被用到 现在, 设其中其中至少有一个交点 (没有交点的情况可以简单利用圆心距大于半径之和来判定), 换言之存在某个 $A_s$ 使得存在对应的点 $(x_s, y_s)$ 于两个圆的方程都成立, 即
Posted at Nov 15 2009 - 14:56:09
Permanent Link:
/p/14
Load full text
|
Post tags:
Algorithm
C
Geometry
|
0
Page 1
|