Linux嵌入式开发与应用

欢迎来到Linux嵌入式开发与应用>>   | 首页 资源中心 | Database | Linux | 生活趣谈 | 嵌入式开发 | 一般分类 | ITPUB论坛

数据源:以编程方式配置 ODBC 数据源

发表人:upczap | 发表时间: 2007年十二月08日, 20:39

数据源:以编程方式配置 ODBC 数据源

本主题说明如何用编程方式配置开放式数据库连接 (ODBC) 数据源名称。这增加了访问数据的灵活性,不必强制用户显式地使用 ODBC 管理器或其他程序来指定数据源名称。

通常,如果关联的数据库管理系统 (DBMS) 支持用 ODBC 管理器来创建数据源,则用户运行 ODBC 管理器来创建数据源。

用 ODBC 管理器创建 Microsoft Access ODBC 数据源时,有两个选择:可以选择现有的 .mdb 文件,或者创建新的 .mdb 文件。没有从 MFC ODBC 应用程序创建 .mdb 文件的编程方式。因此,如果您的应用程序要求将数据放到 Microsoft Access 数据源(.mdb 文件)中,最可行的办法是有一个空的 .mdb 文件,需要时可以使用或复制它。

但是,许多 DBMS 允许以编程的方式创建数据源。一些数据源维护数据库的目录规范。也就是说,目录是一个数据源,数据源中的每个表都存储在单独的文件中(对于 dBASE,每个表都是一个 .dbf 文件)。其他 ODBC 数据库的驱动程序,如 Microsoft Access 和 SQL Server,要求在满足一些特定的条件之后才能建立数据源。例如,使用 SQL Server ODBC 驱动程序时,需要建立了一台 SQL Server 计算机。

SQLConfigDataSource 实例

下面的示例用 ::SQLConfigDataSource ODBC API 函数创建一个新的称为“New Excel Data Source”的 Excel 数据源:

SQLConfigDataSource(NULL,ODBC_ADD_DSN, "Excel Files (*.xls)", 
                   "DSN=New Excel Data Source" 
                   "Description=New Excel Data Source" 
                   "FileType=Excel" 
                   "DataDirectory=C:EXCELDIR" 
                   "MaxScanRows=20");

注意,数据源实际上是一个目录 (C:EXCELDIR),这个目录必须存在。Excel 驱动程序使用目录作为其数据源,使用文件作为单独的表(一个表就是一个 .xls 文件)。

有关创建表的更多信息,请参见 数据源:以编程方式在 ODBC 数据源中创建表

下面的内容讨论需要传递给 ::SQLConfigDataSource ODBC API 函数的参数。若要使用 ::SQLConfigDataSource,必须包括 Odbcinst.h 头文件并使用 Odbcinst.lib 导入库。另外,Odbccp32.dll(对于 16 位系统为 Odbcinst.dll)必须在运行时路径中。

您可以使用 ODBC 管理器或类似的实用程序来创建 ODBC 数据源名称。但是,有时从应用程序直接创建数据源名称获得访问权,而不需要用户运行单独的实用程序的做法更可取。

ODBC 管理器(通常安装在“控制面板”中)将一些项放在 Windows 注册表(对于 16 位系统,放在 Odbc.ini 文件)中,从而新建数据源。ODBC 驱动程序管理器将查询这个文件,以获得有关数据源的所需信息。了解哪些信息要放在注册表中很重要,因为您需要通过调用 ::SQLConfigDataSource 来提供这些信息。

尽管不用 ::SQLConfigDataSource 也可以将这些信息直接写到注册表,但是任何这样做的应用程序都依赖于驱动程序管理器当前用来维护其数据的技术。如果 ODBC 驱动程序管理器以后的修订版使用不同的方法来维护有关数据源的记录,使用这项技术的所有应用程序都将会出现问题。通常建议您在提供了 API 函数的情况下使用 API 函数。例如,如果使用 ::SQLConfigDataSource 函数,可以将代码从 16 位系统移植到 32 位系统,因为该函数可以正确地写入 Odbc.ini 文件或注册表。

SQLConfigDataSource 参数

下面解释 ::SQLConfigDataSource 函数的参数。其中大部分内容摘自随 Visual C++ 1.5 版本和更高版本一同提供的“ODBC API 程序员参考”

函数原型

BOOL SQLConfigDataSource(HWND hwndParent,UINT fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);

备注

参数及其用法

hwndParent

指定为任何对话框的所有者的窗口。ODBC 驱动程序管理器或者特定 ODBC 驱动程序将创建这些对话框以从用户处获得有关新数据源的附加信息。如果 lpszAttributes 参数没有提供足够的信息,则显示一个对话框。hwndParent 参数可以为 NULL

lpszDriver

驱动程序的描述。这是显示给用户的名称,不是物理驱动器的名称 (DLL)。

lpszAttributes

格式为“键名=值”的属性列表。这些字符串用 Null 结束符分开,列表结尾有 2 个连续的 Null 结束符。这些属性主要是要为新数据源填入到注册表中的默认驱动程序特定项。ODBC API 参考中有关该函数没有提到的重要的一项是指定新数据源名称的“DSN”(“数据源名称”)。其他项都是针对新数据源驱动程序的。因为驱动程序会用对话框提示用户提供新值,所以通常没有必要提供所有的项。(将 hwndParent 设置成 NULL 将导致这种情况。)您也可以显式地提供默认值以便不提示用户进行输入。

使用 ODBC 管理器确定 lpszDriver 参数的驱动程序说明

  1. 运行 ODBC 管理器。

  2. 单击“添加”

这会提供已安装的驱动程序及其说明的列表。将此说明作为 lpszDriver 参数使用。请注意,应使用整个说明,包括说明中的文件扩展名和括号(如果有),如“Excel Files (*.xls)”。

另一种方法是检查注册表(对于 16 位系统,为 Odbcinst.ini 文件),在注册表项“ODBC Drivers”(或在 Odbcinst.ini 的 [ODBC Drivers] 节)下有一个所有驱动程序项和说明的列表。

一种查找 lpszAttributes 参数的键名和值的方法是:查看已配置的数据源(多半是由 ODBC 管理器配置的数据源)的 Odbc.ini 文件。

查找 lpszAttributes 参数的键名及值

  1. 运行 Windows 注册表编辑器(或者对于 16 位系统,打开 Odbc.ini 文件)。

  2. 使用以下方法之一查找 ODBC 数据源的信息:

    • 对于 32 位系统,在左窗格中查找键 HKEY_CURRENT_USERSoftwareODBCODBC.INIODBC Data Sources

      右边窗格列出了形式为“pub: REG_SZ:<data source name>”的项,这里 <data source name> 是已用您要使用的驱动程序的所需设置配置好的数据源。选择要用的数据源,例如 SQL Server。字符串“pub:”后的项依次为在 lpszAttributes 参数中要使用的键名和值。

    • 对于 16 位系统,在 Odbc.ini 文件中查找带 [<data source name>] 标记的节。

      该行之后的各行的格式为“键名=值”。这些正是在 lpszAttributes 参数中要用的项。

您也许想查看要使用的特定驱动程序的文档。可以在驱动程序的联机帮助中找到有用的信息。运行 ODBC 管理器可访问驱动程序的联机帮助。在 Windows NT、Windows 3.1 或 Windows 95 上,这些帮助文件通常位于 WINDOWSSYSTEM 目录下。

获得 ODBC 驱动程序的联机帮助

  1. 运行 ODBC 管理器。

  2. 单击“添加”

  3. 选择驱动程序名称。

  4. 单击“确定”。

在 ODBC 管理器显示为该特定驱动程序创建新数据源的信息时,单击“帮助”。这就打开了该特定驱动程序的帮助文件,该帮助文件一般包含有关使用驱动程序的重要信息。

有关更多信息,请参见 Installer DLL 函数参考


VC中用于调试程序的几个宏的使用技巧

发表人:upczap | 发表时间: 2007年十二月08日, 20:36

VC中用于调试程序的几个宏的使用技巧

一、TRACE
当选择了Debug目标,并且afxTraceEnabled变量被置为TRUE时,TRACE宏也就随之被激活了。但在程序的Release版本中,它们是被完全禁止的。下面是一个典型的TRACE语句:
int nCount =9;
CString strDesc("total");
TRACE("Count =%d,Description =%sn",nCount,strDesc);
可以看到,TRACE语句的工作方式有点像C语言中的printf语句,TRACE宏参数的个数是可变的,因此使用起来非常容易。如果查看MFC的源代码,你根本找不到TRACE宏,而只能看到TRACE0TRACE1TRACE2TRACE3宏,它们的参数分别为0123
二、ASSERT
如果你设计了一个函数,该函数需要一个指向文档对象的指针做参数,但是你却错误地用一个视图指针调用了这个函数。这个假的地址将导致视数据的破坏。现在,这种类型的问题可以被完全避免,只要在该函数的开始处实现一个ASSERT测试,用来检测该指针是否真正指向一个文档对象。一般来讲,编程者在每个函数的开始处均应例行公事地使用assertionASSERT宏将会判断表达式,如果一个表达式为真,执行将继续,否则,程序将显示一条消息并且暂停,你可以选择忽视这条错误并继续、终止这个程序或者是跳到Debug器中。下面一例演示了如何使用一个ASSERT宏去验证一个语句。
void foo( char p, int size )
{
ASSERT( p != 0 ); //确认缓冲区的指针是有效的
ASSERT( ( size >= 100 ); //确认缓冲区至少有100个字节
// Do the foo calculation
}
这些语句不产生任何代码,除非—DEBUG处理器标志被设置。Visual C++只在Debug版本设置这些标志,而在Release版本不定义这些标志。当—DEBUG被定义时,两个assertions将产生如下代码:
//ASSERT( p != 0 );
do{
if( !(p != 0) && AfxAssertFailedLine(FILE,LINE) )
AfxDebugBreak();
}while(0);
//ASSERT((size = 100);
do{
if(!(size = 100) &&AfxAssertFailedLine(FILE,LINE))
AfxDebugBreak();
}while(0);
Dowhile循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。
Dowhile循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。
AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。
AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。
三、VERIFY
因为assertion只能在程序的Debug版本中起作用,在表达式中不可以包含赋值语句、增加语句(++)或者是减少语句(--),因为,这些语句实际改变数据。可有时你可能想要验证一个能动的表达式,使用一个赋值语句。那么就到了用VERIFY宏来替代ASSERT。例如:
void foo(char p, int size )
{
char q;
VERIFY(q = p);
ASSERT((size = 100);
// Do the foo calculation
// Do the foo calculation
}
Debug模式下,ASSERTVERIFY是一回事,但是在Release模式下,VERIFY宏仍然测试表达式而assertion却不起任何作用。可以说,在Release模式下,ASSERT语句被删除了。
请注意,如果你在一个ASSERT语句中错误地使用了一个能动的表达式,编译器将不做任何警告地忽略它。在Release模式下,该表达式就会被无声息地删除掉,这将会导致程序的错误运行。由于Release版的程序通常不包含Debug信息,这类错误将很难被发现。

QT开发

发表人:upczap | 发表时间: 2007年六月24日, 22:19

QT开发群 42164870 ,欢迎QT开发爱好者加入该群!!

有经验大家共同学习!


自制一小型的linux系统mylinux

发表人:upczap | 发表时间: 2007年六月02日, 18:57

通过grub引导程序引导编译后的内核文件bzImage,把制作的文件系统变成ramdisk镜像文件,通过内存加载镜像文件完成文件系统的注入;其中的命令通过编译busybox生成bin,sbin文件;注意:

内核文件+文件系统镜像文件+grub引导程序<=1440K

详细步骤

1. 软盘上安装引导器(grub

具体操作如下:

# mke2fs /dev/fd0

创建了 ext2 文件

# mount /dev/fd0 /mnt/floppy

现在,创建一些目录,并将一些关键文件复制到软盘:

# mkdir /mnt/floppy/boot

# mkdir /mnt/floppy/boot/grub

# cp /boot/grub/stage1 /mnt/floppy/boot/grub

# cp /boot/grub/stage2 /mnt/floppy/boot/grub

运行grub命令

grub> 提示符处,输入:

grub> root (fd0)

grub> setup (fd0)

grub> quit

引导盘完成。

2 配置busybox

新建一个目录存放资料:

#mkdir /floppylinux

make menuconfig配置busybox

#cp busybox-1.00.tar.gz /floppylinux

#cd /floppylinux

#tar xvfz busybox-1.00.tar.gz

#cd busybox-1.00

#make menuconfig

下面是需要编译进busybox的功能选项,

General Configuration应该选的选项

Show verbose applet usage messages

Runtime SUID/SGID configuration via /etc/busybox.conf

Build Options

Build BusyBox as a static binary (no shared libs)

Installation Options

Don't use /usr

其他选项都是一些linux基本命令选项默认

配置好后退出并保存.

编译并安装busybox

#make

#make install

编译好后在busybox目录下生成子目录_install,里面的内容:

drwxr-xr-x 2 root root 4096 11 24 15:28 bin

lrwxrwxrwx 1 root root 11 11 24 15:28 linuxrc -> bin/busybox

drwxr-xr-x 2 root root 4096 11 24 15:28 sbin

其中可执行文件busyboxbin目录下,其他的都是指向他的符号链接.

3 制作根文件系统

建立临时目录,该目录为软盘的文件系统

#mkdir //tmp/floppy-linux

busybox下的_install目录下的文件复制过来:

#cp ./_install /tmp/floppy-linux –r

#cd /tmp/floppy-linux

# mkdir dev etc etc/init.d proc mnt tmp var

# chmod 755 dev etc etc/init.d bin mnt tmp var

# chmod 555 proc

# cd dev

# mknod tty c 5 0

# mknod console c 5 1

# chmod 666 tty console

# mknod tty0 c 4 0

# chmod 666 tty0

# mknod ram0 b 1 0

# chmod 600 ram0

# mknod fd0 b 2 0

# chmod 600 fd0

# mknod null c 1 3

# chmod 666 null

建启动配置文件:

/etc/inittab,

/etc/init.d/rcS,

/etc/fstab

/boot/grub.conf

/boot/menu.lst

其中

#ln –s grub.conf menu.lst

initab:

::sysinit:/etc/init.d/rcS

::askfirst:/bin/sh

rcS:

#!/bin/sh

mount –a

# chmod 755 rc.sysinit

fstab:

proc /proc proc defaults 0 0

grub.conf:

timeout 10

default 0

title FloppyLinux   root (fd0)  kernel /boot/bzImage  initrd /initrd.img.gz

4 制作Ramdisk的镜像文件:

# dd if=/dev/zero of=/dev/ram1

dd: 正在写入 /dev/ram1’: 设备上没有空间

读入了 8193+0 个块

输出了 8192+0 个块

#mke2fs -m0 /dev/ram1

#mkdir /mnt/ram

#mount /dev/ram1 /mnt/ram

将先前做好的floppylinux根文件系统拷贝到ram1.

#cp -R /tmp/floppy-linux /* /mnt/ram

#umount /dev/ram1

# dd if=/dev/ram1 of=/tmp/floppy-linux /initrd.img

# file initrd.img

initrd.img: Linux rev 1.0 ext2 filesystem data

loop设备来把他重新挂装到文件系统里:

# mount -o loop initrd.img /mnt/ram/

查看/mnt/ram下的内容,/tmp/floppy-linux /下的一模一样

# ls /mnt/ram

bin dev etc lost+found mnt proc sbin tmp var

#umount /mnt/ram

压缩initrd.img印象文件

# gzip -v9 initrd.img

initrd.img: 90.1% -- replaced with initrd.img.gz

查看压缩后的大小:

# ls -lh initrd.img.gz

只有406K

5编译linux系统内核

#cp linux-2.4.20.bz2 /usr/src/

#cd /usr/src

#tar xfvj linux-2.4.20.bz2

#ln -s linux-2.4.20 linux

进入linux源代码目录:

#cd linux

清理源代码树:

#make mrproper

运行配置程序:

#make menuconfig

code maturity level options

先选择N,当我们配置好常规的东西,要加入framebuffer支持时再将这一项选择Y,如果不在code maturity level options选择为Y,将不能配置framebuffer.

Loadable module support

选择N,为了简化系统的制作,我在这个项目中不选择可加载内核模块的支持.

processor type and features

processor family 中选择386CPU

其他选项都选择N.

General setup

networking support 选择Y

PCI support 选择Y

System V ipc 选择Y

systrl support选择Y

kernel support for ELF 选择Y

其余内容都可以选择N,

chnology devices (MTD)

Parallel port support

Plug and Play configuration

以上三个大项中的所有内容选择N

block devices

Normal floppy disk support

Loopback device support

RAM disk support

initial RAM disk (initrd) support

Per partition statics in /proc/partitions

以上几项选择Y,其余全部选择N.

Multi-device support (RAID and LVM)

Cryptography support (CryptoAPI)

这两个大项全部选择N

Networking options

这一大项中,只需要把下列项目编译进内核:

Packet socket :mmapped IO

TCP/IP networking

Telephony Support 选择N

ATA/IDE/MFM/RLL support

选择Y,然后下面的'IDE,ATA and ATAPI Block Devices'按钮就被激活

下面几项选择Y,其余都可以是N.

Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support

Include IDE/ATA-2 DISK support

Auto-Geometry Resizing support

Include IDE/ATA CDROM support

SCSI support

Fusion MPT device support

IEEE 1394(FireWire) support

I2O device support

全部选择N

Network device support

选择Y

然后点Ethernet(10 or 100 Mbit)按钮选择网卡驱动(Reltek8139)

Amateur Radio support

IrDA (infrared) support

ISDN subsystem

Old CD-ROM drivers (not SCSI,not IDE)

Input core support

全部选择N

Charcter devices

除了Virtual terminalSupport for console on terminal两项,其他全选N

Multimedia devices

Crypto Hardware support

全部选择N.

File sytems

选择其中有三个:

/proc file system support.

Second extended fs support BabyLinux的基本文件系统.

ISO 9660 CDROM filesytem support用光盘.

Console drivers

.前面三个全部选择Y,

Frame-buffer support按钮是灰色的不能选,别急,回到第一个大选项:

Code maturity level options 选择Y,就可以激活这个按钮了.

下面几个选项需要选择Y:

Support for framebuffer devices

VESA VGA graphics console

Support only 8 pixels wide fonts

剩下的几个大项全部选N

保存后退出,配置程序会自动生成一个隐藏的配置文件.config

#make dep

#make bzImage

bzImage654K

6 整合启动盘

#cp bzImage /mnt/floppy/boot

#cp /tmp/floppy-linux/initrd.img.gz /mnt/floppy

全部文件(文件夹)如下:

/lost+found/

/boot/

/boot/grub/

/boot/grub/stage1

/boot/grub/stage2

/boot/grub/menu.lst

/boot/grub/grub.conf

/boot/bzImage

/initrd.img.gz

五实验遇到问题与总结

1,在制作启动盘的时候,运行grub命令后,进入grub界面后,运行:

grub>setup (fd0)

结果有时可能会出错,可能是软盘出问题,换张软盘

2,软盘制作完成后,重启机器,在grub开始进行引导时可能出现错误:

Error 16: Inconsistent filesystem strutwre

这个也是软盘的问题

3,在编译的内核时,有时可能不通过,在Redhet9.0(kernel-2.4.20)下用gcc-3.3.3就能编译通过2.4.20版本的内核,而在Fedora Core 2下用gcc-3.4.2就不行,可能是gcc的版本问题。


Linux下的多线程编程

发表人:upczap | 发表时间: 2007年五月28日, 08:39

1 引言
  线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。
  为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
  下面我们先来尝试编写一个简单的多线程程序。
2 简单的多线程编程
  Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序example1.c。

/* example.c*/
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.n");
}

int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!n");
exit (1);
}
for(i=0;i<3;i++)
printf("This is the main process.n");
pthread_join(id,NULL);
return (0);
}

我们编译此程序:
gcc example1.c -lpthread -o example1
运行example1,我们得到如下结果:
This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
再次运行,我们可能得到如下结果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.

  前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,  pthread_create和pthread_join,并声明了一个pthread_t型的变量。
  pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
  typedef unsigned long int pthread_t;
  它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
  函数pthread_join用来等待一个线程的结束。函数原型为:
  extern int pthread_join __P ((pthread_t __th, void **__thread_return));
  第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
  唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
  在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。

3 修改线程的属性
  在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
  属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
  关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
  设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
  另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, &param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, &param);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
  
4 线程的数据处理
  和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线程数据时的有关知识。

4.1 线程数据
  在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
  和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
  创建键的函数原型为:
  extern int pthread_key_create __P ((pthread_key_t *__key,
  void (*__destr_function) (void *)));
  第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。

  在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。
/* 声明一个键*/
pthread_key_t myWinKey;
/* 函数 createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 调用函数createMyKey,创建键*/
pthread_once ( & once, createMyKey) ;
/*win指向一个新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific ( myWinKey, win);
}

/* 函数 createMyKey,创建一个键,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}

/* 函数 freeWinKey,释放空间*/
void freeWinKey ( Fl_Window * win){
delete win;
}

  这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
  extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
  extern void *pthread_getspecific __P ((pthread_key_t __key));
  这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。

4.2 互斥锁
  互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
  我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
  这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
  pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。
  上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

4.3 条件变量  前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
  条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
  其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 
  函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:
  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));
  线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
  另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));
  它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
  函数pthread_cond_signal()的原型为:
  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
  它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}

increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
  count值为0
时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
  函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。

4.4 信号量  信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
  函数sem_destroy(sem_t *sem)用来释放信号量sem。
  下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/* File sem.c */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 从文件1.dat读取数据,每读一次,信号量加一*/
void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*从文件2.dat读取数据*/
void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/
void HandleData1(void){
while(1){
sem_wait(&sem);
printf("Plus:%d+%d=%dn",stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
--size;
}
}

void HandleData2(void){
while(1){
sem_wait(&sem);
printf("Multiply:%d*%d=%dn",stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void *)HandleData1,NULL);
pthread_create(&t2,NULL,(void *)HandleData2,NULL);
pthread_create(&t3,NULL,(void *)ReadData1,NULL);
pthread_create(&t4,NULL,(void *)ReadData2,NULL);
/* 防止程序过早退出,让它在此无限期等待*/
pthread_join(t1,NULL);
}

  在Linux下,我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。 我们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我们运行sem,得到如下的结果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11

  从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是多线程编程要注意的问题。

5 小结
  多线程编程是一个很有意思也很有用的技术,使用多线程技术的网络蚂蚁是目前最常用的下载工具之一,使用多线程技术的grep比单线程的grep要快上几倍,类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序来。


写给25岁以上的单身男性的100条忠告

发表人:upczap | 发表时间: 2007年四月11日, 21:14

写给25岁以上的单身男性的100条忠告

1你可以不拥有任何东西,除了对生活的激情。
2然后是钱。
3努力赚钱。
4有了钱,可以拥有很多女人的身体。
5没有钱,要拥有很多女人的心。
6如果身体和心你都无法得到,那只能说明你的无能。
7英雄和浪子,取长补短。
8该英雄时要英雄,该浪子时得浪子。
9喝酒,抽烟。一个都不能上瘾。
10赌,女人,权力。追求,但不能沉沦。
11热爱体育。如果你不热爱你就不是男人。
12即使你爱体育爱得发疯。也不能抱着体育生活一辈子。
13善待你的家人。
14网络游戏,浅尝辄止。
15有分寸,懂得控制自己。
16厨艺精湛,但不轻易下厨。
17如果厨艺不佳,刀功一定要好。
18得到女人的心,先满足她的胃。
19再给她可以倚靠的肩膀。
20依赖。女人的致命处。
21想让女人嫁给你,给她安全感。经济上和精神上的双重安全。
22不要以为每个女人都很傻。
23如果不打算骗女人一生一世。就不要轻易骗她。
24如果是善意的谎言,要设法让她明白你的良苦用心。
25如果不爱她,就不要娶她。除非你能保证对她好。
26如果背叛了她,就尽量补偿她。
27女人的寿命比男人长,她会恨男人一直恨到死。
28最好不要得罪女人。
29补偿最好的方式是钱。
30连钱都不要的女人,要小心。
31追到一个女人很容易,可是摆脱掉却很难。追求之前请三思。
32不要跟玩不起的人玩。
33就算是尝试一夜情,也不要选择去P C。
34对象的选择和客户一样,慎重。
35不要轻易给女人承诺。说者无心,听者有意。
36尊重女人的最好方式就是坦诚对她。
37即使你是个流氓,也要流氓得坦坦荡荡。
38没有一个女人,能比你的母亲重要。
39小时糊涂,大事不糊涂。
40身材魁梧,动作温柔。
41找一个理解你的人做妻子。
42找一个了解你的女人做知已。
43作为成熟男人,应该清楚得不到的才是最好的。
所以,该舍时要舍得。
44不要打女人。有这种冲动前先看看《妇女权益保障法》。
45不要谈太多次恋爱。否则你自己都会厌倦不堪。
46不要见一个爱一个,爱得太多,你的爱就要贬值。
47“我爱你”这三个字不必挂在嘴边,用行动来证明吧。
48“我爱你”堵住所有废话的最后一击。话要用在刀刃上。
49拥抱或者吻。肢体语言永远比语言更有力度。
50对上司,可以服从,可以抗议。就是不能卑躬屈膝。
51买一栋房子。即使是按揭。只要你不打算当无根浪子。
52再买一栋。即使是借钱。用于投资。
53炒股。不为赚钱,只为证明自己的判断力。
54即使是输掉一栋房子,也要笑着自嘲:我不是一个输不起的人。
55买车。先买二手车。如果你技术很好或者有很多钱则另当别论。
56哪儿摔倒,哪儿站起来再摔几次。
57没有才华,也要有气魄。
58不要西装革履,也不必着装休闲。把衬衫扣子解掉三颗,就是一种随意。
59神采飞扬,不修边幅。
60穿西装时记着把商标撕掉。
61笑,在任何人面前。
62让你周围的人因为你的存在而感到快乐。
63怕硬可以,欺软不行。
64选择婚姻,而不是婚姻选择你。
65着装整洁,不必非得把皮鞋擦得锃亮。
66随意。不必往头发上喷发胶或者定型水。
67看一个女人虚荣不虚荣,就剃上光头衣冠不整地跟她一起走在大街上。注意她的表情。
68一支玫瑰,或者一句甜言蜜语。关键时候,派上用场。女人是很容易满足的。
69因为需要才喜欢,还是因为喜欢才需要。分清楚。
70想跟女人分手时,设法让她先提出来。
71大智若愚。傻与不傻,先看你会不会装傻。
72做一个出色的男人,先学会哄。
73哄上司,哄同事,哄老人,哄女人,哄孩子。
74如果以上你通通都能哄到,那么恭喜你,你的人生一定值得你炫耀。
75魅力源于实力。
76能让别人跟着你走,是你的魅力所在。
77脾气温和,性格好斗。
78内敛。该出手时再出手。
79同流,但不合污。
80异想天开。没有异想,何来天开?
81沉稳。不乏可爱。
82如果实在长相严肃,至少可以带点儿冷幽默。
83有自己的立场和观点。不一定要表明出来。
84说到做到。做到再说。
85旅游。不必期待艳遇。
86沉默。有些话是不必说出来的。
87在女人面前,永远不要解释,只有认错。
88邀请不熟的女士吃饭,要找些天时地利的借口。
89像个君子,即使你不是。
90先赚钱,再谈爱。
91同居。如果你享受这种习惯。
92享受眼前的同时,想想以后。
93冷静和超然,即使是在危难时刻。装也要装出来。
94得之淡然,失之泰然。一切皆顺其自然。
95顺然自然,并不代表不去争取。
96不要跟一般人一般见识,即使是特殊人也一样。
97不要骂脏话。
98侮辱一个人最好的方式就是置之不理。侮辱,而不是责骂。
99认真。但不顽固。
100好好生活。你的生命是一次性的。


15所名校计算机专业之特色分析

发表人:upczap | 发表时间: 2007年四月11日, 20:41

15所名校计算机专业之特色分析

作者:◎ 清华大学 倪铭辰

随着IT时代的到来,几乎所有院校都开设了计算机系。计算机专业领域很多,虽然对于本科生来说,基本上是宽领域的全面培养,但是由于有各个学校的特色存在,同学们往往会在强势领域有更多的收获。

清华大学:在拥有多位院士的清华大学差不多在所有方向都有相应的强人撑着,相比之下,应用和系统结构更强些,某些方向已经处于世界领先水平。优势领域包括多媒体、网络技术等。同时拥有模式识别与智能控制国家重点学科。

北京大学:在软件方面尤其在中文视觉听觉信息处理方面有优势,由王选和杨芙清两位院士曾领导的方正和青鸟证实了北大在中国当前软件界的实力。硬件方面有一位院士王阳元。

上海交通大学:上海交通大学近年来在走上坡路,软件方向的发展很快,模式识别与智能控制也是国家重点学科。优势领域包括系统软件、网络信息获取与处理、并行与分布理论、数据库等。

南京大学:如果说20世纪80年代的软件专业是并蒂莲花的话,那么现在的南大完全可以说是一枝独秀,由孙钟秀院士领导的软件学科甚至已经涉及到大型操作系统的设计。优势领域包括软件自动化与形式化方法、分布计算与并行处理、系统软件及其信息安全、多媒体技术、人工智能与知识工程、数据库技术、语言信息处理等。拥有软件新技术国家重点实验室。

哈尔滨工业大学:深处寒冷北方的哈工大能保持几十年的超强实力可以说是个奇迹,作为当年唯一的计算机应用重点学科,哈工大培养出了大量的人才。现在他们与许多学校和研究机构搞学科并建,吸纳了很多外来人才,增强了哈工大的整体实力。目前哈工大承担的项目经费已超亿元。他们的优势领域包括:智能机器人、CIMS与系统集成、智能化中文信息处理机器翻译技术、计算机网络及系统安全防护技术等。

东北大学:建有软件工程国家级工程研究中心,它的软件方向大多与数据库有关,东软集团的发展证实了东大软件的实力。张嗣瀛教授是中科院院士。优势领域:智能控制与智能机器人系统、诊断与容错控制、CIMS中生产计划与存储管理的建模、宽带计算机网络、分布式多媒体信息处理技术、数据库及其应用技术、嵌入式系统技术等。

国防科技大学:这是一所不太为人所了解的学校,就像一个淡泊名利的世外高人,我们在感叹她的高大的同时,也会有一种难以接近的感觉。由3位院士领导的系统结构方向曾经研制出了让中国人骄傲的银河系列大型计算机。国防科技大学的优势领域大多和硬件相关。设有网络技术与信息安全研究所及并行与分布处理国家重点实验室。

浙江大学:前任校长潘云鹤是浙大计算机系唯一一位院士(现为中国工程院副院长),他所领衔创建的计算机图形学、智能CAD在国内无其右者,而且浙大开放式的校风,吸纳贤才的远见,使其越来越受到人们的认可。浙大建有计算机辅助设计与图形学国家重点实验室。优势领域还有CAD/CIMS、虚拟现实、网络与多媒体、产品创新设计、智能信息与人机交互技术等。

东南大学:中国工程院院士顾冠群教授为首。计算机网络、数据库及信息系统软件技术与人工智能CIMS技术是东南大学的优势领域。建有国家863/CIMS网络和数据库实验室、教育部网络和信息集成技术实验室、中国教育和科研计算机网华东(北)地区网络中心。

北京航天航空大学:是发展势头最猛的学校之一,在人工智能、软件工程和体系结构、虚拟现实与多媒体技术方向有优势。建有由李未院士领导的软件开发环境国家重点实验室,教育部虚拟现实新技术重点实验室、教育部软件网上联合研发中心。

有一些学校的计算机专业是在基础学科如数学、电子(通信)的基础上发展起来的,也就是随着计算机发展的狂热而转型来的。数学带动软件发展,电子通信则对硬件的发展奠定了良好的基础。
以数学专业发展软件专业的学校有复旦大学、南开大学、中国科技大学等。以电子通讯专业发展硬件专业的学校有西安电子科技大学、西安交通大学、北京邮电大学等。


选择排序讲解

发表人:upczap | 发表时间: 2007年四月07日, 22:07

选择排序讲解

////////////////////////////////////////////////////////////////////

#i nclude<stdio.h>

void main(){

void sort(int x[],int n);

int *p,i,a[10];

p=a;

for(i=0;i<10;i++)scanf("%d",p++);

p=a;

sort(p,10);

for(p=a,i=0;i<10;i++){

printf("%5d",*p);

p++;

}

}

void sort(int x[],int n){

int i,j,k,t;

for(i=0;i<n-1;i++){

k=i;

for(j=i+1;j<n;j++)

if(x[j]>x[k])k=j;

if(k!=i){

t=x[i];

x[i]=x[k];

x[k]=t;

}

}

}

//////////////////////Li同学发送给我的源程序有若干笔误,已修改

mail中该同学提到:

这个是课本指针那一章的,是用选择法对10个数从大到小排序的.我看不懂后面的那个被调函数尤其是那个if(k!=i),这个条件不是必然成立的吗?老师具体给我说下这个程序吧.

下面专门讲解一下这个程序。

选择排序方法是基本的排序算法之一。其算法思路简单,容易构建,缺点是执行效率不如冒泡法、希尔排序法等。

选择排序法的核心思想是从待排序数据序列中选择最大(小)的那个,将其放在最前。比如对于10个数据(编号为array[0]-array[9]),如下:

12 56 -9 456 69 321 -987 52 63 10

首先从array[0]-array[9]中选择最大的那个,将其放在array[0]

其次从array[1]-array[9]中选择最大的那个,将其放在array[1]

再次从array[2]-array[9]中选择最大的那个,将其放在array[2]

……

最后从array[8]-array[9] 中选择最大的那个,将其放在array[8]

很显然,经过9轮选择,整个数组就会按降序排列,这就是选择排序法的算法流程。

这需要用一个二重循环来实现。内层循环的本质是寻找特定范围数据的最大者,并将其放在约定位置。

将该算法以更一般的形式描述:

n个数组元素需要从大到小排序(编号为array[0]-array[n-1])。则需要n-1轮选择。

首先从array[0]-array[n-1]中选择最大的那个,将其放在array[0]

其次从array[1]-array[n-1]中选择最大的那个,将其放在array[1]

再次从array[2]-array[n-1]中选择最大的那个,将其放在array[2]

……

最后从array[n-2]-array[n-1]中选择最大的那个,将其放在array[8]

可以看出,这n-1次循环的规律是一样的。选择排序算法可以描述为:

void sort(int array[],int n){

int i;

for(i=0;i<=n-2;i++)

{array[i]-array[n-2]中选择最大的放在array[i];}

}

那么下面的任务就是实现{array[i]-array[n-2]中选择最大的放在array[i];}了。略。

注释讲解如下:

///////////////////////////////////////////////////////////////////

#i nclude<stdio.h>

void main(){

void sort(int x[],int n);

int *p,i,a[10];//主函数中声明指针变量p,循环变量i,数组a

p=a;//p指向a的首地址即a[0]

for(i=0;i<10;i++)scanf("%d",p++);//通过指针p间接访问数组元素,接受值放在数组中

p=a;//重新使指针变量指向数组首地址,非常重要

sort(p,10);//调用选择法排序函数sort(int*,int)

for(p=a,i=0;i<10;i++){//for循环用来打印已排序的数组元素,p=a没有必要

printf("%5d",*p);

p++;

}

}

void sort(int x[],int n){

int i,j,k,t;//k用来指向最大的元素

for(i=0;i<n-1;i++){//n个数组元素,i=0~n-2,n-1轮,在x[i]-x[n-2]中选择最大的放在x[i];

k=i;//默认x[i]为最大,k指向x[i]

for(j=i+1;j<n;j++)

if(x[j]>x[k])k=j;//若有元素大于x[k],则更新k,使k始终指向最大的元素

if(k!=i){//k最初指向i,若k值没有变化(k==i),说明x[i]就是x[i]-x[n-2]中的最大值,不用再交换;若k!=i,说明k值有更新就不再等于i,需要将x[k]即最大值交换到x[i]

t=x[i];

x[i]=x[k];

x[k]=t;

}

}

}

/////////////////////////////////////////////////////////////

至于Li同学所说“k!=i必然成立”是不正确的。k==i是有可能的,如array[i]-array[n-1]的最大值恰好是array[i],则k==i。这时候就不必要(而非不能)交换,因为array[k]就是array[i]

sort函数执行实例:

28 356 53 -9 93 815 -815 8 9 15降序排列(array[0]-array[9])。

//////////////////第一轮(i==0),从array[i]-array[9]中选择最大的,放在a[i]

i==0,k==5,array[i]<==>array[k]

第一轮过后:

815 356 53 -9 93 28 -815 8 9 15

//////////////////第二轮(i==1),从array[i]-array[9]中选择最大的,放在a[i]

i==1,k==1,不必交换

第二轮过后:

815 356 53 -9 93 28 -815 8 9 15

//////////////////第三轮(i==2),从array[i]-array[9]中选择最大的,放在a[i]

i==2,k==4,array[i]<==>array[k]

第三轮过后:

815 356 93 -9 53 28 -815 8 9 15

//////////////////第四轮(i==3),从array[i]-array[9]中选择最大的,放在a[i]

i==3,k==4,array[i]<==>array[k]

第四轮过后:

815 356 93 53 -9 28 -815 8 9 15

//////////////////第五轮(i==4),从array[i]-array[9]中选择最大的,放在a[i]

i==4,k==5,array[i]<==>array[k]

第五轮过后:

815 356 93 53 28 -9 -815 8 9 15

//////////////////第六轮(i==5),从array[i]-array[9]中选择最大的,放在a[i]

i==5,k==9,array[i]<==>array[k]

第六轮过后:

815 356 93 53 28 15 -815 8 9 -9

//////////////////第七轮(i==6),从array[i]-array[9]中选择最大的,放在a[i]

i==6,k==8,array[i]<==>array[k]

第七轮过后:

815 356 93 53 28 15 9 8 -815 -9

//////////////////第八轮(i==7),从array[i]-array[9]中选择最大的,放在a[i]

i==7,k==7,不必交换

第八轮过后:

815 356 93 53 28 15 9 8 -815 -9

//////////////////第九轮(i==8),从array[i]-array[9]中选择最大的,放在a[i]

i==8,k==9, array[i]<==>array[k]

第九轮过后:

815 356 93 53 28 15 9 8 -9 -815

/////////////////完成


Google 的21道面试问题

发表人:upczap | 发表时间: 2007年四月05日, 19:58

Google 的21道面试问题

10月底,Google在美国《麻省技术评论》、《LinuxJournal》、《Mensa》、《今日物理》等几本专业杂志上,刊登了一份“Google实验室能力倾向测试”。
  试卷开头,蛊惑地写着“试试看!把答案寄回Google,你有希望去Google总部参观,并成为我们其中一员”。

我看了这些题目,虽然古怪,但是也不算有困难,有兴趣的人可以做完了邮寄给google公司,也许会得到一个工作机会呢。


  1、解答下面的隐藏等式,其中的M和E的值可以互换,但不允许第一位是0:

  WWWDOT - GOOGLE = DOTCOM

  2、用一个俳句(一种日本短诗,每句有一个与季节有关的词)来建立模型,借此预测网络搜索流量的季节性变化;

  3、
  1

  1 1

  2 1

  1 2 1 1

  1 1 1 2 2 1

  下一行是什么?

  4、你正处于一个全部由崎岖小路构成的迷宫里,手里有一个满是灰尘的笔记本,可以无线上网,但是信号很弱。与此同时,一些阴森可怕、毫无生气的妖怪在你身边游荡。你会怎么做呢?

  (1)毫无目的的四处游荡,到处碰壁,直到被迷宫里的妖怪吃掉。

  (2)用笔记本作为挖掘工具,打穿地面直接进入下一关。

  (3)玩网络游戏《魔法骑兵》,直至电池耗尽,你也心灰意冷。

  (4)使用笔记本画出迷宫的节点地图,找到出路。

  (5)发送简历给Google,告诉主管妖怪你选择退出,随后你就回到现实世界。

  5、Unix有何缺陷?你准备如何补救?

  6、在Google工作的第一天,你发现身边的同事竟然是研究生一年级课本的作者,你会:

  (1)主动示好并索取签名。

  (2)不改变坐姿,但放轻打字声音,避免影响她的工作和思考。

  (3)把你每天的麦片和咖啡都留给她享用。

  (4)在她所写的书中找到你最喜欢的内容,并告诉她这些内容已经成为你的座右铭。


  7、下列哪句话最贴切的表达了Google的企业文化?
  (1)我感到很幸运。

  (2)不要干坏事。

  (3)哦,我已经解决了那个问题。

  (4)你身边50英寸之内,必定能找到食物。

  (5)以上皆是。

  8、用3种颜色为20面体上色,每个面一种颜色,有多少种组合?你会选择哪些颜色?


  9、下面是故意留出的空白,请将其填满,使之看起来不那么空。

  10、用1欧姆的电阻组成无限大的两维矩阵,“象棋跳马步”(“日”字对角点)两点之
间的电阻是多少?

  11、现在是星期日下午2点,你正在旧金山著名的湾区。你可以选择去国家公园的红杉
林里徒步旅行,或者参观城市里的文化景观。你会怎么做?

  12、你认为最美的数学等式是什么?

  13、下列哪个团体没有在Google员工中形成?

  (1)女子篮球

  (2)淡**爱好者

  (3)Cricketeers

  (4)诺贝尔奖获得者

  (5)葡萄酒俱乐部

  14、搜索技术的下一个革命性突破是什么?

  15、一个项目组由多少人构成才能达到最优规模?也就是说,一旦超过这一数字,每
增加一个成员项目组的平均生产力就会相应下降。

  (1)1个

  (2)3个

  (3)5个

  (4)11个

  (5)24个

  16、给你一个三角形ABC,请用圆规和尺找出点P,保证三角形ABP、ACP和BCP周长相等。

  17、有这样一个函数,对于任意整数n,都能返回写出0到n之间出现“1”的个数。例如,f(13)=6。请注意f(1)=1,那么下一个能实现f(n)=n的最大数字是什么?

  18、你编写的最酷的黑客程序是什么?

  19、在下面的数列中,下一个数字是多少:10, 9, 60, 90, 70, 66,?

  (1)96

  (2)10的100次方

  (3)以上皆是
  16、给你一个三角形ABC,请用圆规和尺找出点P,保证三角形ABP、ACP和BCP周长相等。

  17、有这样一个函数,对于任意整数n,都能返回写出0到n之间出现“1”的个数。例如,f(13)=6。请注意f(1)=1,那么下一个能实现f(n)=n的最大数字是什么?

  18、你编写的最酷的黑客程序是什么?

  19、在下面的数列中,下一个数字是多少:10, 9, 60, 90, 70, 66,?

  (1)96

  (2)10的100次方

  (3)以上皆是

  (4)以上皆不是

  20、用少于29个词,描述你能带给Google实验室带来的贡献。


1. Solve this cryptic equation, realizing of course that values for M and E could be interchanged. No leading zeros are allowed.
WWWDOT - GOOGLE = DOTCOM

2. Write a haiku describing possible methods for predicting search traffic seasonality.

3.
1
1 1
2 1
1 2 1 1
1 1 1 2 2 1

What is the next line?

4. You are in a maze of twisty little passages, all alike. There is a dusty laptop here with a weak wireless connection. There are dull, lifeless gnomes strolling about. hat dost thou do?

A) Wander aimlessly, bumping into obstacles until you are eaten by a grue.
B) Use the laptop as a digging device to tunnel to the next level.
C) Play MPoRPG until the battery dies along with your hopes.
D) Use the computer to map the nodes of the maze and discover an exit path.
E) Email your resume to Google, tell the lead gnome you quit and find yourself
in whole different world.

5. What's broken with Unix? How would you fix it?

6. On your first day at Google, you discover that your cubicle mate wrote the extbook you used as a primary resource in your first year of graduate school. Do you:

A) Fawn obsequiously and ask if you can have an autograph.
B) Sit perfectly still and use only soft keystrokes to avoid disturbing her concentration.
C) Leave her daily offerings of granola and English toffee from the food bins.
D) Quote your favorite formula from the textbook and explain how it's now your mantra.
E) Show her how example 17b could have been solved with 34 fewer lines of code.
7. Which of the following expresses Google over-arching philosophy?

A) "I'm feeling lucky"
B) "Don't be evil"
C) "Oh, I already fixed that"
D) "You should never be more than 50 feet from food"
E) All of the above

8. How many different ways can you color an icosahedron with one of three colors on each face?

What colors would you choose?

9. This space left intentionally blank. Please fill it with something that improves upon emptiness.

10.On an infinite, two-dimensional, rectangular lattice of 1-ohm resistors, what is the resistance between two nodes that are a knight's move away?

11.It's 2 PM on a sunny Sunday afternoon in the Bay Area. You're minutes from the Pacific Ocean, redwood forest hiking trails and world class cultural attractions. What do you do?

12.In your opinion, what is the most beautiful math equation ever derived?

13. Which of the following is NOT an actual interest group formed by Google employees?

A. Women's basketball
B. Buffy fans
C.ricketeers
D. Nobel winners
E. Wine club

14.What will be the next great improvement in search technology?

15.What is the optimal size of a project team, above which additional members do not contribute productivity equivalent to the percentage increase in the staff size?
A) 1
B) 3
C) 5
D) 11
E) 24

16.Given a triangle ABC, how would you use only a compass and straight edge to find a point P such that triangles ABP, ACP and BCP have equal perimeters? (Assume that ABC is constructed so that a solution does exist.)

17.Consider a function..which, for a given whole number n, returns the number of ones required when writing out all numbers between 0 and n. For example, f(13)=6. Notice that f(1)=1. What is the next largest n such that f(n)=n?
18.What's the coolest hack you've ever written?

19.'Tis known in refined company, that choosing K things out of N can be done in ways as many as choosing N minus K from N: I pick K, you the remaining.

Find though a cooler bijection, where you show a knack uncanny, of making your choices contain all K of mine. Oh, for pedantry: let K be no more than half N.

20.What number comes next in the sequence:
10, 9, 60, 90, 70, 66,?

A)96
B) 1000000000000000000000000000000000
0000000000000000000000000000000000
000000000000000000000000000000000
C) Either of the above
D) None of the above

21.In 29 words or fewer, describe what you would strive to accomplish if you worked at Google Labs.


Linux下的段错误产生的原因及调试方法

发表人:upczap | 发表时间: 2007年三月28日, 16:43

Linux下的段错误产生的原因及调试方法

简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址.

一般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了.

在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的

1)访问系统数据区,尤其是往 系统保护的内存地址写数据
最常见就是给一个指针以0地址
2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域

解决方法

我们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难 免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个 内存访问越界的错误谈谈如何快速定位这些"段错误"的语句。
下面将就以下的一个存在段错误的程序介绍几种调试方法:

1 dummy_function (void)
2 {
3 unsigned char *ptr = 0x00;
4 *ptr = 0x00;
5 }
6
7 int main (void)
8 {
9 dummy_function ();
10
11 return 0;
12 }
作为一个熟练的C/C++程序员,以上代码的bug应该是很清楚的,因为它尝试操作地址为0的内存区域,而这个内存区域通常是不可访问的禁区,当然就会出错了。我们尝试编译运行它:
xiaosuo@gentux test $ ./a.out
段错误
果然不出所料,它出错并退出了。
1.利用gdb逐步查找段错误:
这种方法也是被大众所熟知并广泛采用的方法,首先我们需要一个带有调试信息的可执行程序,所以我们加上“-g -rdynamic"的参数进行编译,然后用gdb调试运行这个新编译的程序,具体步骤如下:
xiaosuo@gentux test $ gcc -g -rdynamic d.c
xiaosuo@gentux test $ gdb ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) r
Starting program: /home/xiaosuo/test/a.out

Program received signal SIGSEGV, Segmentation fault.
0x08048524 in dummy_function () at d.c:4
4 *ptr = 0x00;
(gdb)
哦?!好像不用一步步调试我们就找到了出错位置d.c文件的第4行,其实就是如此的简单。
从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man 7 signal),我们知道SIGSEGV默认handler的动作是打印”段错误"的出错信息,并产生Core文件,由此我们又产生了方法二。
2.分析Core文件:
Core文件是什么呢?
The default action of certain signals is to cause a process to terminate and produce a core dump file, a disk file containing an image of the process's memory at the time of termination. A list of the signals which cause a process to dump core can be found in signal(7).
以 上资料摘自man page(man 5 core)。不过奇怪了,我的系统上并没有找到core文件。后来,忆起为了渐少系统上的拉圾文件的数量(本人有些洁癖,这也是我喜欢Gentoo的原因 之一),禁止了core文件的生成,查看了以下果真如此,将系统的core文件的大小限制在512K大小,再试:
xiaosuo@gentux test $ ulimit -c
0
xiaosuo@gentux test $ ulimit -c 1000
xiaosuo@gentux test $ ulimit -c
1000
xiaosuo@gentux test $ ./a.out
段错误 (core dumped)
xiaosuo@gentux test $ ls
a.out core d.c f.c g.c pango.c test_iconv.c test_regex.c
core文件终于产生了,用gdb调试一下看看吧:
xiaosuo@gentux test $ gdb ./a.out core
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".


warning: Can't read pathname for load map: 输入/输出错误.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048524 in dummy_function () at d.c:4
4 *ptr = 0x00;
哇,好历害,还是一步就定位到了错误所在地,佩服一下Linux/Unix系统的此类设计。
接着考虑下去,以前用windows系统下的ie的时侯,有时打开某些网页,会出现“运行时错误”,这个时侯如果恰好你的机器上又装有windows的编译器的话,他会弹出来一个对话框,问你是否进行调试,如果你选择是,编译器将被打开,并进入调试状态,开始调试。
Linux下如何做到这些呢?我的大脑飞速地旋转着,有了,让它在SIGSEGV的handler中调用gdb,于是第三个方法又诞生了:
3.段错误时启动调试:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

void dump(int signo)
{
char buf[1024];
char cmd[1024];
FILE *fh;

snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
if(!(fh = fopen(buf, "r")))
exit(0);
if(!fgets(buf, sizeof(buf), fh))
exit(0);
fclose(fh);
if(buf[strlen(buf) - 1] == 'n')
buf[strlen(buf) - 1] = '';
snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
system(cmd);

exit(0);
}

void
dummy_function (void)
{
unsigned char *ptr = 0x00;
*ptr = 0x00;
}

int
main (void)
{
signal(SIGSEGV, &dump);
dummy_function ();

return 0;
}
编译运行效果如下:
xiaosuo@gentux test $ gcc -g -rdynamic f.c
xiaosuo@gentux test $ ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

Attaching to program: /home/xiaosuo/test/a.out, process 9563
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xffffe410 in __kernel_vsyscall ()
(gdb) bt
#0 0xffffe410 in __kernel_vsyscall ()
#1 0xb7ee4b53 in waitpid () from /lib/libc.so.6
#2 0xb7e925c9 in strtold_l () from /lib/libc.so.6
#3 0x08048830 in dump (signo=11) at f.c:22
#4 <signal handler called>
#5 0x0804884c in dummy_function () at f.c:31
#6 0x08048886 in main () at f.c:38
怎么样?是不是依旧很酷?
以上方法都是在系统上有gdb的前提下进行的,如果没有呢?其实glibc为我们提供了此类能够dump栈内容的函数簇,详见/usr/include/execinfo.h(这些函数都没有提供man page,难怪我们找不到),另外你也可以通过gnu的手册进行学习。
4.利用backtrace和objdump进行分析:
重写的代码如下:
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
unsigned char *ptr = 0x00;
*ptr = 0x00;
}

void dump(int signo)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);

printf ("Obtained %zd stack frames.n", size);

for (i = 0; i < size; i++)
printf ("%sn", strings[i]);

free (strings);

exit(0);
}

int
main (void)
{
signal(SIGSEGV, &dump);
dummy_function ();

return 0;
}
编译运行结果如下:
xiaosuo@gentux test $ gcc -g -rdynamic g.c
xiaosuo@gentux test $ ./a.out
Obtained 5 stack frames.
./a.out(dump+0x19) [0x80486c2]
[0xffffe420]
./a.out(main+0x35) [0x804876f]
/lib/libc.so.6(__libc_start_main+0xe6) [0xb7e02866]
./a.out [0x8048601]
这次你可能有些失望,似乎没能给出足够的信息来标示错误,不急,先看看能分析出来什么吧,用objdump反汇编程序,找到地址0x804876f对应的代码位置:
xiaosuo@gentux test $ objdump -d a.out

8048765: e8 02 fe ff ff call 804856c <signal@plt>
804876a: e8 25 ff ff ff call 8048694 <dummy_function>
804876f: b8 00 00 00 00 mov $0x0,%eax
8048774: c9 leave
我们还是找到了在哪个函数(dummy_function)中出错的,信息已然不是很完整,不过有总比没有好的啊!
后记:
本文给出了分析"段错误"的几种方法,不要认为这是与孔乙己先生的"回"字四种写法一样的哦,因为每种方法都有其自身的适用范围和适用环境,请酌情使用,或遵医嘱。


Berkeley DB和SQLite

发表人:upczap | 发表时间: 2007年三月27日, 11:51

Berkeley DB和SQLite

下面介绍的是两个开放源代码的嵌入式数据库,Berkeley DB和SQLite。同时侧重介绍如何应用Java连接这两种嵌入式数据库。

一. Berkeley DB

1. 简介

Berkeley DB是一款健壮的,高速的工业级嵌入式数据库产品,你可以在它的官方主页(见参考链接一)上发现很多知名的公司都采用了这款嵌入式数据库。 Berkeley DB的一个很重要的特点是就是高速存储。在高流量,高并发的情况下,Berkeley DB要比非嵌入式的数据库表现得更加出色。所以在一些技术实现上,Berkeley DB被作为大型关系数据库的中间数据缓冲层,用来快速的保存数据,可能会在适当的时刻再导入到大型数据库中,进而应用大型数据库所提供的更为高级的特性。

Berkeley DB虽然是开源的产品,但对某些条件下的商业性应用,却不是免费的,而且价格颇为昂贵。这些商业条件排除了开源的情况,不发放分布版本的情况,等等。比如,如果你的程序是开放源代码的或者仅仅应用到单一的网站上,在这种情况下,Berkeley DB是免费的。

2. 获得Java与Berkeley DB的接口

Berkeley DB目前的版本是4.1.25,自带了Java接口。下载的压缩包中包含C和Java语言的源代码和编译配置文件。在Windows平台,可以用MS Visual C++ 6.0或MS VC.Net编译。用VC6编译的操作如下介绍:在源代码的build_win32路径下打开VC的工程文件,之后在Build菜单中的Set Active Configuration选择db_java win32 release编译选项。在VC的Tools菜单Options选项中指定JNI.H等Java本地化接口编译时所需要头文件的位置。你会在JDK的 include路径下找到这些头文件, 例如加入的路径可能会是这样的:C:jdk1.4include和C:jdk1.4include win32。最后在Tools菜单中Options选项还要设置Javac.exe和Jar.exe的执行路径,这个设置会使VC开发环境也能调用 Java编译器,从而在VC环境下直接完成对Java接口类的编译和打包。在编译后,在release路径下的文件中找到db.jar, libdb41.dll, libdb_java41.dll,这三个文件组成了Berkeley DB的Java接口程序包。

3. 应用Java与Berkeley DB的接口

Berkeley DB并不是一个关系型的数据库。不能应用标准的SQL语句对数据库操作,对它的操作要调用专用的API实现。这些API提供了查询、插入、删除等功能。比如com.sleepycat.db.Db类代表数据库对象。Db类的put()方法完成的是插入功能,get()方法完成的是读出数据的功能。 com.sleepycat.db.Dbc是Berkeley DB的游标类,提供了遍历数据库记录的功能。

Berkeley DB每一个记录都有一个键值和对应的数据值,而键值和数据必须是类com.sleepycat.db.Dbt的对象或其子类的对象。Dbt提供了一些方法可以将byte数组或Object对象保存到Dbt的对象中去。比如,Dbt类中的set_data(byte[])或set_object (Object)方法。注意到目前Berkeley DB中的Java API命名方法并不符合Java的命名规范,比如set_data()方法应该命名为setData()方法。Berkeley DB许诺在下一个版本中会提供符合命名规范的Java API。

Berkeley DB对任何存入的数据都是直接原样存储到数据文件中去,无论其是二进制数据还是ASCII或Unicode等编码的文本。通常可以利用这一特性和Java串行化的概念方便的进行数据的存取。例如声明一个类


public class AccountInfo implements Serializable{
//帐户信息
public String loginName;
public String password;
public boolean auotLogin;
}




在这个AccountInfo类中仅仅包含了数据项的定义。我们完全可以将这个类看作数据库的表中字段定义。可以用Berkeley DB保存AccountInfo对象的串行化二进制数据,以此来保存这个对象中的变量值。在操作中,先对Dbt的对象调用set_object (AccountInfo)方法,而后把这个Dbt对象作为一条纪录保存到表中。当然,我们也可以应用继承Dbt类的方法来完成对数据的保存。

下面这段简单代码演示如何将数据存入到数据库中,然后再用游标对象浏览全部数据。


//注意,下面的程序的忽略了对异常处理,写入数据初始化等等一些代码,请在适当修改后再编
//译运行它
Db dbFile = null;
//生成Db对象
dbFile = new Db(null, 0);
//用BTree方式打开数据库,库文件是在c:/temp下的mydata.db文件,表名是employee
//如果数据库不存在,则自动生成一个新的数据库。
dbFile.open(null,"c:tempmydata.db","employee",Db.DB_BTREE,
Db.DB_CREATE,0);
Dbt key = new Dbt();
Dbt data = new Dbt();
//向库文件中插入一条数据,如果已经存在,打印出错信息
if (dbFile.put(null,key, data, Db.DB_APPEND) == Db.DB_KEYEXIST) {
System.out.println("Key already exists.");
}
//关闭数据文件
dbFile.close(0);
//重新打开数据文件
dbFile = new Db(null, 0);
dbFile.open(null, "c:tempmydata.db", "employee", Db.DB_UNKNOWN,
0, 0644);
// 声明一个数据库游标Dbc对象iterator
Dbc iterator = dbFile.cursor(null, 0);
// 遍历整个表
Dbt key = new Dbt();
while (iterator.get(key, data, Db.DB_NEXT) == 0)
{
System.out.println("reading");
}
//关闭游标和数据文件
iterator.close();
dbFile.close(0);




在运行Berkeley DB的程序时勿必在系统环境变量PATH中设置libdb41.dll和 libdb_java41.dll所在的路径。

4. Berkeley DB的存储模式

Berkeley DB提供了四种存储数据的模式:Btree,Hash,Queue和Recno。在打开数据库的时候要指定一种存储模式,比如上例中open()方法中的参数Db.DB_BTREE就是指定以Btree模式打开数据库。

Btree模式是以排序的二叉树的方式存储,Hash是以线性哈希表的方式存储。Queue用逻辑记录号做为键值,以定长的数据为记录值。 Recno方式也以逻辑记录号做为键值,但可以保存定长或变长的记录值。这里提到的逻辑记录号有两种,可变的和固定的。可变逻辑记录号会根据数据记录的增加与删除做相应的变化。比如在数据库中共有100条记录,如果删除第80条记录,那么第81条记录的逻辑记录号会自动变成80,以此类推,第100条记录逻辑记录号会变成99。固定的逻辑记录号则无论数据库如何操作都不会有变化。Queue模式下,逻辑记录号只能是固定方式。Recno模式则可通过配置来选择是采用那种类型的记录号作为键值。Btree模式也可以通过设置,将可变的逻辑记录号做为键值。

这几种存储模式各有优缺点,要根据具体的需求来选择。当键值不想用逻辑记录号时Btree或Hash是必须的选择。Btree方式比较适合连续的顺序读取,比如,当键值是时间值,如果经常有从某一时间点开始连续读取后继的记录的操作,Btree是一种很好的选择。对随机的跳跃式读取,Hash模式则更为恰当。Queue和Recno都以记录号为键值,但前者适合先进先出的读取方式。Recno则通常是存取变长文本记录的理想存储模式。

5. Berkeley DB Environment的概念

Berkeley DB Environment为一组数据库同时提供参数设置。更为重要的是,如果要应用更高级的特性,必须要使用Environment功能,比如在想要对保存的数据进行加密存储时。

6. 更多特点

除了最基本的插入、查询、删除功能以外,Berkeley DB还提供了一些特性,比如Transaction,数据加密,同步加锁控制,错误日志等功能。下面的图片是Berkeley DB功能示意图。

二. SQLite

1. 简介

相信PHP的开发人员一定不会对SQLite感到陌生,因为在PHP5中已经集成了这个轻巧的内嵌式数据库产品。SQLite与Berkeley DB相比,在操作语句上更类似关系型数据库的产品。绝大多数标准的SQL92语句SQLite都能支持。

SQLite的版权允许无任何限制的应用,包括商业性的产品。在参考链接二上提供的SQLite官方主站上可以下载到编译后的SQLite程序。但推荐应用CVS工具下载最新版本的SQLite源代码。如果在*nux平台下,可直接用make编译。如果在Windows平台,常用的有两种方法,一是应用在Windows平台下的Linux仿真程序,如MingW或Cygwin提供的make来编译。二是应用MS VC平台编译。后者设置略有麻烦,但可仿照参考链接五上提供的MS VC6工程文件的样例。应用到这个样例的时候,要注意的是由于SQLite源代码在不断更新,如果直接应用样例所提供的VC6工程文件编译会出现一些问题,读者要根据具体的情况稍微调整一下编译的设置。

2. 编译第三方Java接口

SQLite源代码是C,而且官方网站上只提供了C和Tcl语言的接口。为了应用Java接口,要采用第三方的接口驱程,可在参考链接三中找到这个Java接口程序。这个接口提供了两种连接SQLite的方式:一是直接用JNI技术调用SQLite的C语言接口,这种方式要求开发人员要对 SQLite本身的API也有一定的了解。在第二种方式中,接口程序实现了Java标准规范的JDBC接口,这样开发人员只要对JDBC有了解就可以了。

下面介绍在Windows系统MS VC6环境中编译SQLite Java接口(同时包括JNI和JDBC两个接口)的过程。如果你对C语言编译的设置很熟悉,可以跳这这段介绍。

第一步先把SQLite源代码编译成Lib静态库文件。具体的步骤可以直接应用下面参考链接中提供的MS VC6工作区文件,其中有一个编译SQLite到静态库的设置。编译成功后得到SQLite.lib文件。第二步要建立一个新的VC DLL项目,然后和上面介绍的Berkeley DB在编译Java本地化接口的设置一样,在VC的Tools菜单Options选项中指定JNI.H等JNI编译所要的头文件位置。同时还要指定 sqlite.h头文件位置,这个文件是在生成SQLite静态库的时候自动生成的,可以在SQLite.lib文件所在的工作区目录下面找到它,例如加入的路径为C:sqlitemsvc6。然后在Project菜单的setting选项设置Link到SQLite.lib库文件,并再次在 Tools菜单中Options指定SQLite.lib的查找路径。注意有些情况下可能要设置予编译选项HAVE_SQLITE_COMPILE以便使用SQLite中VM的一些功能。编译成功后可得到Sqlite_jni.dll文件。

第三方接口库中的Java代码包含JNI接口和多个版本的JDBC接口程序,可根据你的JRE的版本选择相应的JDBC程序。编译这些Java代码的过程这里就不做叙述了。

编译后的Java类包加上前面得到的Sqlite_jni.dll文件,组成了SQLite的Java接口库,在应用Java语言调用JDBC或JNI接口时,都是通过应用Java的本地化技术调用Sqlite_jni.dll文件,完成对SQLite数据库的操作。

3. 应用JNI直接调用SQLite功能

下面这段代码演示如何应用JNI接口操作SQLite。可以看到Database类的exec()方法是执行SQL语句的关键:


Database db = new Database();
try {
//打开数据库
db.open("c:tempmydata.slt", 0666);
db.interrupt();
db.busy_timeout(1000);
db.busy_handler(null);
db.exec("create table account (name varchar(10),gale boolean)"
,result);
db.exec("insert into account values('steve','m')", result);
db.exec("select * from account",result);
//关闭数据库
db.close();
} catch (Exception e) {
e.printStackTrace();
}




4. 应用JDBC连接SQLite

用"SQLite.JDBCDriver"作为JDBC的驱动程序类名。连接JDBC的URL格式为jdbc:sqlite:/path。这里的path为指定到SQLite数据库文件的路径,例如:


jdbc:sqlite://dirA/dirB/dbfile
jdbc:sqlite://DRIVE:/dirA/dirB/dbfile
jdbc:sqlite://COMPUTERNAME/shareA/dirB/dbfile




参考下面的应用JDBC连接SQLite的例程:


//声明JDBC驱动程序
Class clz = Class.forName("SQLite.JDBCDriver");
//连接数据库
Connection conn = DriverManager.getConnection("jdbc:sqlite:/c:/temp/e2.db");
Statement stmt = conn.createStatement();
//生成person表,包含名子和年龄字段
stmt.execute("create table person (name varchar(100),age int)");
//插入数据
stmt.execute("insert into person values('steve',25)");
//用SQL语句读出数据
result = stmt.executeQuery("select * from person");
while(result.next()){
System.out.println(result.getString(1));
System.out.println(result.getInt(2));
}




运行程序时要在Java.exe命令行加入选项java.library.path指定到Sqlite_jni.dll所在的路径。例如,如果 Sqlite_jni.dll放在c:sqliteNative 路径下面,运行类com.e2one.MyClass 的命令行将会是这样:java -Djava.library.path=c:sqliteNative com.e2one.MyClass。

5. SQLite的特点

SQLite是无数据类型的数据库。虽然在生成表结构的时候,要声明每个域的数据类型,但SQLite并不做任何检查。开发人员要靠自己的程序控制输入与读出数据的类型是正确的。这里有一个例外,就是当主键为整型值时,如果要插入一个非整型值时会产生异常。另外,虽然在插入或读出数据的时候是不区分类型的,但在比较的时候,不同数据类型是有区别的。比如:


CREATE TABLE MyTable(a INTEGER, b TEXT);
INSERT INTO MyTable VALUES(0,0);




当执行下面的查询:


SELECT count(*) FROM MyTable WHERE a=='00';




会返回一条记录,因为字段a的类型是整型,而数字00与0是相等的。

而执行下面的查询则不会返回记录:


SELECT count(*) FROM MyTable WHERE b=='00';




因为字段b是字符类型,字符"00"与"0"是不相等的。

SQLite提供了对Transaction的支持。应用Transaction即保证了数据的完整性,也会提高运行速度,因为多条语句一起提交给数据库的速度会比一条一条的提交方式更快。

对二进制数据,SQLite不能直接保存。但可以先将二进制的数据转换成ASCII编码,然后再保存。Base64编码机制是最常见的把二进制数据转换成ASCII编码的手段。在SQLite的C语言代码encode.c中提供了Base64编码的功能。对Java而言,在参考链接六中提供的 Apache的XML RPC项目中可以找到一个Base64编码的例子。

上面介绍了两个比较常见的嵌入式数据库,Berkeley DB速度极快,可靠性高,但学习起来有一定难度。SQLite则简单易用,速度也很快,又可以应用标准的JDBC连接,但它功能却照Berkeley略有逊色,比如加密功能、二进制数据的处理等。

参考资料

http://www.sleepycat.com ,Berkeley DB的官方主页。


http://www.sqlite.org , SQLite的官方主页。


http://www.ch-werner.de/javasqlite , 提供SQLite JNI接口程序和一个JDBC接口。


http://www.iturls.com/TechHotspot/TH_embeddedDB.asp 提供了一个很详细的嵌入式数据库产品及一些相关介绍。


http://jrepp.com/src.shtml Jake Repp的个人网站,上面提供了一个在MS VC6下编译SQLite的工程文件。


http://ws.apache.org/xmlrpc/ 可以在这个工程上找到将二进制数据用Base64编码方式转换成ASCII编码。


SQLITE3 在VC/MFC 中使用的一点体会

发表人:upczap | 发表时间: 2007年三月27日, 11:48

SQLITE3 在VC/MFC 中使用的一点体会

SQLITE简介:

This is an extension for the SQLite Embeddable SQL Database Engine. SQLite is a C library that implements an embeddable SQL database engine. Programs that link with the SQLite library can have SQL database access without running a separate RDBMS process.

SQLite is not a client library used to connect to a big database server. SQLite is the server. The SQLite library reads and writes directly to and from the database files on disk.

vc工程目录下 设置 Link L/对象类模块 sqlite3.lib ,c/c++ 分类 precompiled header 选择不使用预补偿页眉

首先将SQLITE3的七个文件放在和vc工程文件同一目录下,在工程中加入 CppSQLite3DB.cppCppSQLite3DB.h文件

# include "CppSQLite3.h"

extern CppSQLite3DB db;/////数据库对象

remove("c:test.db");

db.open("c:test.db");///打开数据库文件

建立表格:

db.execDML("create table Customer(CustomerName char(50), RoomNumber int, CustomerId int, ComeTime int,Money int);");

db.execDML("create table Room(RoomNumber int, RoomPrice int, RoomState char[20]);");

db.execDML("create table Manager(ManagerName char[20],PassWord int);");

插入数据:

string szCmd;

string szName = m_data1;

szCmd = "insert into Customer values(" ;

szCmd +="'";

szCmd +=szName;

szCmd +="'";

szCmd +=",";

sprintf(a,"%d",m_data2);

szCmd += a;

szCmd += ",";

sprintf(b,"%d",m_data3);

szCmd +=b;

szCmd +=","

sprintf(c,"%d",m_data4);

szCmd += c;

szCmd += ",";

sprintf(d,"%d",m_data5);

szCmd += d;

szCmd += ");";

db.execDML(szCmd.c_str());/////////////插入SQL语句

MessageBox("提交成功,请继续!");

更新数据:

char k[20];//提交后更新房间信息

string szCkd;

szCkd="update Room set RoomState = 'notnull' where RoomNumber=";

sprintf(k,"%d",m_data2);

szCkd +=k;

szCkd +=";";

db.execDML(szCkd.c_str()); //////////插入SQL语句

查询数据:

CppSQLite3Query q = db.execQuery("select * from Customer;");

while(!q.eof()){

UpdateData(true);

if(m_data1==q.getIntField(1))

{

s.m_data1=q.getStringField(0);

s.m_data2=q.getIntField(1);

s.m_data3=q.getIntField(2);

s.m_data4=q.getFloatField(3);

s.m_data5=q.getIntField(4);

s.DoModal();

UpdateData(false);

break;}

q.nextRow();

if(q.eof()==true)

MessageBox("本旅馆无该房间!");

}

删除数据:

char a[20];

sprintf(a,"%d",m_data2);

string szCmd;

szCmd="delete from Room where RoomNumber =" ;

szCmd+=a;

szCmd+=";";

int b=0;

b=db.execDML(szCmd.c_str());

if(b==0)

MessageBox("操作错误,没有该房间信息");

else

MessageBox("删除房间信息成功");

以上为SQLITE3在窗体交互下简易实现几种数据库基本功能的简单说明!

vc工程目录下 设置 Link L/对象类模块 sqlite3.lib ,c/c++ 分类 precompiled header 选择不使用预补偿页眉

首先将SQLITE3的七个文件放在和vc工程文件同一目录下,在工程中加入 CppSQLite3DB.cppCppSQLite3DB.h文件

# include "CppSQLite3.h"

extern CppSQLite3DB db;/////数据库对象

remove("c:test.db");

db.open("c:test.db");///打开数据库文件

建立表格:

db.execDML("create table Customer(CustomerName char(50), RoomNumber int, CustomerId int, ComeTime int,Money int);");

db.execDML("create table Room(RoomNumber int, RoomPrice int, RoomState char[20]);");

db.execDML("create table Manager(ManagerName char[20],PassWord int);");

插入数据:

string szCmd;

string szName = m_data1;

szCmd = "insert into Customer values(" ;

szCmd +="'";

szCmd +=szName;

szCmd +="'";

szCmd +=",";

sprintf(a,"%d",m_data2);

szCmd += a;

szCmd += ",";

sprintf(b,"%d",m_data3);

szCmd +=b;

szCmd +=","

sprintf(c,"%d",m_data4);

szCmd += c;

szCmd += ",";

sprintf(d,"%d",m_data5);

szCmd += d;

szCmd += ");";

db.execDML(szCmd.c_str());/////////////插入SQL语句

MessageBox("提交成功,请继续!");

更新数据:

char k[20];//提交后更新房间信息

string szCkd;

szCkd="update Room set RoomState = 'notnull' where RoomNumber=";

sprintf(k,"%d",m_data2);

szCkd +=k;

szCkd +=";";

db.execDML(szCkd.c_str()); //////////插入SQL语句

查询数据:

CppSQLite3Query q = db.execQuery("select * from Customer;");

while(!q.eof()){

UpdateData(true);

if(m_data1==q.getIntField(1))

{

s.m_data1=q.getStringField(0);

s.m_data2=q.getIntField(1);

s.m_data3=q.getIntField(2);

s.m_data4=q.getFloatField(3);

s.m_data5=q.getIntField(4);

s.DoModal();

UpdateData(false);

break;}

q.nextRow();

if(q.eof()==true)

MessageBox("本旅馆无该房间!");

}

删除数据:

char a[20];

sprintf(a,"%d",m_data2);

string szCmd;

szCmd="delete from Room where RoomNumber =" ;

szCmd+=a;

szCmd+=";";

int b=0;

b=db.execDML(szCmd.c_str());

if(b==0)

MessageBox("操作错误,没有该房间信息");

else

MessageBox("删除房间信息成功");

以上为SQLITE3在窗体交互下简易实现几种数据库基本功能的简单说明!

vc工程目录下 设置 Link L/对象类模块 sqlite3.lib , c/c++ 分类 precompiled header 选择不使用预补偿页眉

首先将SQLITE3的七个文件放在和vc工程文件同一目录下,在工程中加入 CppSQLite3DB.cppCppSQLite3DB.h文件

# include "CppSQLite3.h"

extern CppSQLite3DB db;/////数据库对象

remove("c:test.db");

db.open("c:test.db");///打开数据库文件

建立表格:

db.execDML("create table Customer(CustomerName char(50), RoomNumber int, CustomerId int, ComeTime int,Money int);");

db.execDML("create table Room(RoomNumber int, RoomPrice int, RoomState char[20]);");

db.execDML("create table Manager(ManagerName char[20],PassWord int);");

插入数据:

string szCmd;

string szName = m_data1;

szCmd = "insert into Customer values(" ;

szCmd +="'";

szCmd +=szName;

szCmd +="'";

szCmd +=",";

sprintf(a,"%d",m_data2);

szCmd += a;

szCmd += ",";

sprintf(b,"%d",m_data3);

szCmd +=b;

szCmd +=","

sprintf(c,"%d",m_data4);

szCmd += c;

szCmd += ",";

sprintf(d,"%d",m_data5);

szCmd += d;

szCmd += ");";

db.execDML(szCmd.c_str());/////////////插入SQL语句

MessageBox("提交成功,请继续!");

更新数据:

char k[20];//提交后更新房间信息

string szCkd;

szCkd="update Room set RoomState = 'notnull' where RoomNumber=";

sprintf(k,"%d",m_data2);

szCkd +=k;

szCkd +=";";

db.execDML(szCkd.c_str()); //////////插入SQL语句

查询数据:

CppSQLite3Query q = db.execQuery("select * from Customer;");

while(!q.eof()){

UpdateData(true);

if(m_data1==q.getIntField(1))

{

s.m_data1=q.getStringField(0);

s.m_data2=q.getIntField(1);

s.m_data3=q.getIntField(2);

s.m_data4=q.getFloatField(3);

s.m_data5=q.getIntField(4);

s.DoModal();

UpdateData(false);

break;}

q.nextRow();

if(q.eof()==true)

MessageBox("本旅馆无该房间!");

}

删除数据:

char a[20];

sprintf(a,"%d",m_data2);

string szCmd;

szCmd="delete from Room where RoomNumber =" ;

szCmd+=a;

szCmd+=";";

int b=0;

b=db.execDML(szCmd.c_str());

if(b==0)

MessageBox("操作错误,没有该房间信息");

else

MessageBox("删除房间信息成功");

以上为SQLITE3在窗体交互下简易实现几种数据库基本功能的简单说明!


C/C++中调用SQLITE3的基本步骤

发表人:upczap | 发表时间: 2007年三月27日, 11:45

C/C++中调用SQLITE3的基本步骤

Sqlite是一个面向嵌入式系统的数据库,编译完成只有200K,同时支持2T的数据记录。对于嵌入式设备是一个很好的数据库引擎。本文通过一个小例子说明如何在C与C++调用Sqlite API完成数据库的创建、插入数据与查询数据。本文的开发环境为(Redhat9.0 + Qtopia2.1.2 + Sqlite3)

安装Sqlite3:

www.sqlite.org上下载Sqlite3.2.2运源代码,依照Readme中的步骤:

tar xzf sqlite3.2.2.tar.gz

mkdir bld

cd bld

../sqlite3.2.2/configure

make

make install

然后在shell下运行 sqlite3 test.db命令可以检验是否已经安装成功。

创建数据库:

sqlite3 *pDB = NULL;
char * errMsg = NULL;

//打开一个数据库,如果改数据库不存在,则创建一个名字为databaseName的数据库文件
int rc = sqlite3_open(databaseName, &pDB);

if(rc)
{
cout << " Open the database " << databaseName << " failed" << endl;
}

//如果创建成功,atECt2g*网c;添加表

else
{
cout << "create the database successful!" << endl;

//creat the table
int i;
for(i=1; i<nTableNum; i++)
{

}
//插入一个表,

专件XJor2管}]hlNb]N

;GhlNvn络@[N教P?网

返回值为SQLITE_OK为成功,否则输出出错信息

//函数参数:第一个为操作数据库的指针,第二句为SQL命令字符串

//第三个参数为callback函数,这里没有用,第四个参数为callback函数

//中的第一个参数,第五个为出错信息

rc = sqlite3_exec(pDB, "CREATE TABLE chn_to_eng(chinese QString, english QString)", 0, 0, &errMsg);

if(rc == SQLITE_OK)
cout << "create the chn_to_eng table successful!" << endl;
else
cout << errMsg << endl;

//同上,插入另一个表

rc = sqlite3_exec(pDB, "CREATE TABLE eng_to_chn(english QString, chinese QString)", 0, 0, &errMsg);

if(rc == SQLITE_OK)
cout << "create the eng_to_chn table successful!" << endl;
else
cout << errMsg << endl;

}

、、、、、、

//往表中添加数据

char chn[]="...";

char eng[]="...";

char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES(''''%s'''', ''''%s'''')", chn, eng);

//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);

//查询一条记录

char value[500];

//定义一条查询语句,其中条件为当english为target时的中文记录

//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english=''''%s'''' ", target);

rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);

if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}

.......

}

//callback回调函数print_result_cb的编写,

理EG网`cc_@育:业提Ev

其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果

int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}

print_row(n_columns, column_values);
return 0;
}

void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}

printf("%s", values[i]);

}
printf("n");
}

大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org

void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}

printf("%s", values[i]);

}
printf("n");
}

大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org

--ywwcn@msn.com

char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES(''''%s'''', ''''%s'''')", chn, eng);

//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);

//查询一条记录

char value[500];

//定义一条查询语句,

育11o]wdeZ国|_[zL中

其中条件为当english为target时的中文记录

//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english=''''%s'''' ", target);

rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);

if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}

.......

}

//callback回调函数print_result_cb的编写,

xbSe}育1(8R国%.h

其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果

int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}

print_row(n_columns, column_values);
return 0;
}

void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}

printf("%s", values[i]);

}
printf("n");
}

大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org

--ywwcn@msn.com

void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}

printf("%s", values[i]);

}
printf("n");
}

大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org

--ywwcn@msn.com


Linux 下 C 语言编程

发表人:upczap | 发表时间: 2007年三月26日, 22:52

Linux 下 C 语言编程

Linux的发行版中包含了很多软件开发工具。 它们中的很多是用于 C 和 C++应用程序开发的。 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具。 本文的主旨是介绍如何在 Linux 下使用 C 编译器和其他 C 编程工具, 而非 C 语言编程的教程。 在本文中你将学到以下知识:
· 什么是 C
· GNU C 编译器
· 用 gdb 来调试GCC应用程序
你也能看到随 Linux 发行的其他有用的 C 编程工具。 这些工具包括源程序美化程序(pretty print programs), 附加的调试工具, 函数原型自动生成工具(automatic function prototypers)。
注意: 源程序美化程序(pretty print programs)自动帮你格式化源代码产生始终如一的缩进格式。
什么是 C?
C 是一种在 UNIX 操作系统的早期就被广泛使用的通用编程语言。 它最早是由贝尔实验室的 Dennis Ritchie 为了 UNIX 的辅助开发而写的, 开始时 UNIX 是用汇编语言和一种叫 B 的语言编写的。 从那时候起, C 就成为世界上使用最广泛计算机语言。
C 能在编程领域里得到如此广泛支持的原因有以下一些:
· 它是一种非常通用的语言。 几乎你所能想到的任何一种计算机上都有至少一种能用的 C 编译器。 并且它的语法和函数库在不同的平台上都是统一的, 这个特性对开发者来说很有吸引力。
· 用 C 写的程序执行速度很快。
· C 是所有版本的UNIX上的系统语言。
C 在过去的二十年中有了很大的发展。 在80年代末期美国国家标准协会(American National Standards Institute)发布了一个被称为 ANSI C 的 C 语言标准。这更加保证了将来在不同平台上的 C 的一致性。 在80年代还出现了一种 C 的面向对象的扩展称为 C++。 C++ 将在另一篇文章 "C++ 编程"中描述。
Linux 上可用的 C 编译器是 GNU C 编译器, 它建立在自由软件基金会的编程许可证的基础上, 因此可以自由发布。 你能在 Linux 的发行光盘上找到它。
GNU C 编译器
随 Slackware Linux 发行的 GNU C 编译器(GCC)是一个全功能的 ANSI C 兼容编译器。 如果你熟悉其他操作系统或硬件平台上的一种 C 编译器, 你将能很快地掌握 GCC。 本节将介绍如何使用 GCC 和一些 GCC 编译器最常用的选项。
使用 GCC
通常后跟一些选项和文件名来使用 GCC 编译器。 gcc 命令的基本用法如下:
gcc [options] [filenames]
命令行选项指定的操作将在命令行上每个给出的文件上执行。 下一小节将叙述一些你会最常用到的选项。
GCC 选项
GCC 有超过100个的编译选项可用。 这些选项中的许多你可能永远都不会用到, 但一些主要的选项将会频繁用到。 很多的 GCC 选项包括一个以上的字符。 因此你必须为每个选项指定各自的连字符, 并且就象大多数 Linux 命令一样你不能在一个单独的连字符后跟一组选项。 例如, 下面的两个命令是不同的:
gcc -p -g test.c
gcc -pg test.c
第一条命令告诉 GCC 编译 test.c 时为 prof 命令建立剖析(profile)信息并且把调试信息加入到可执行的文件里。 第二条命令只告诉 GCC 为 gprof 命令建立剖析信息。
当你不用任何选项编译一个程序时, GCC 将会建立(假定编译成功)一个名为 a.out 的可执行文件。 例如, 下面的命令将在当前目录下产生一个叫 a.out 的文件:
gcc test.c
你能用 -o 编译选项来为将产生的可执行文件指定一个文件名来代替 a.out。 例如, 将一个叫 count.c 的 C 程序编译为名叫 count 的可执行文件, 你将输入下面的命令:
gcc -o count count.c
注意: 当你使用 -o 选项时, -o 后面必须跟一个文件名。
GCC 同样有指定编译器处理多少的编译选项。 -c 选项告诉 GCC 仅把源代码编译为目标代码而跳过汇编和连接的步骤。 这个选项使用的非常频繁因为它使得编译多个 C 程序时速度更快并且更易于管理。 缺省时 GCC 建立的目标代码文件有一个 .o 的扩展名。
-S 编译选项告诉 GCC 在为 C 代码产生了汇编语言文件后停止编译。 GCC 产生的汇编语言文件的缺省扩展名是 .s 。 -E 选项指示编译器仅对输入文件进行预处理。 当这个选项被使用时, 预处理器的输出被送到标准输出而不是储存在文件里。
优化选项
当你用 GCC 编译 C 代码时, 它会试着用最少的时间完成编译并且使编译后的代码易于调试。 易于调试意味着编译后的代码与源代码有同样的执行次序, 编译后的代码没有经过优化。 有很多选项可用于告诉 GCC 在耗费更多编译时间和牺牲易调试性的基础上产生更小更快的可执行文件。 这些选项中最典型的是-O 和 -O2 选项。
-O 选项告诉 GCC 对源代码进行基本优化。 这些优化在大多数情况下都会使程序执行的更快。 -O2 选项告诉 GCC 产生尽可能小和尽可能快的代码。 -O2 选项将使编译的速度比使用 -O 时慢。 但通常产生的代码执行速度会更快。
除了 -O 和 -O2 优化选项外, 还有一些低级选项用于产生更快的代码。 这些选项非常的特殊, 而且最好只有当你完全理解这些选项将会对编译后的代码产生什么样的效果时再去使用。 这些选项的详细描述, 请参考 GCC 的指南页, 在命令行上键入 man gcc 。
调试和剖析选项
GCC 支持数种调试和剖析选项。 在这些选项里你会最常用到的是 -g 和 -pg 选项。
-g 选项告诉 GCC 产生能被 GNU 调试器使用的调试信息以便调试你的程序。 GCC 提供了一个很多其他 C 编译器里没有的特性, 在 GCC 里你能使 -g 和 -O (产生优化代码)联用。 这一点非常有用因为你能在与最终产品尽可能相近的情况下调试你的代码。 在你同时使用这两个选项时你必须清楚你所写的某些代码已经在优化时被 GCC 作了改动。 关于调试 C 程序的更多信息请看下一节"用 gdb 调试 C 程序" 。
-pg 选项告诉 GCC 在你的程序里加入额外的代码, 执行时, 产生 gprof 用的剖析信息以显示你的程序的耗时情况。 关于 gprof 的更多信息请参考 "gprof" 一节。
用 gdb 调试 GCC 程序
Linux 包含了一个叫 gdb 的 GNU 调试程序。 gdb 是一个用来调试 C 和 C++ 程序的强力调试器。 它使你能在程序运行时观察程序的内部结构和内存的使用情况。 以下是 gdb 所提供的一些功能:
· 它使你能监视你程序中变量的值。
· 它使你能设置断点以使程序在指定的代码行上停止执行。
· 它使你能一行行的执行你的代码。
在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动并且你将在屏幕上看到类似的内容:
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc.
(gdb)
当你启动 gdb 后, 你能在命令行上指定很多的选项。 你也可以以下面的方式来运行 gdb :
gdb
当你用这种方式运行 gdb , 你能直接指定想要调试的程序。 这将告诉gdb 装入名为 fname 的可执行文件。 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或者与一个正在运行的程序相连。 你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表。
为调试编译代码(Compiling Code for Debugging)
为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息。 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。 gdb 利用这些信息使源代码和机器码相关联。
在编译时用 -g 选项打开调试选项。
gdb 基本命令
gdb 支持很多的命令使你能实现不同的功能。 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 表27.1列出了你在用 gdb 调试时会用到的一些命令。 想了解 gdb 的详细使用请参考 gdb 的指南页。
表 27.1. 基本 gdb 命令.
命令描述
file 装入想要调试的可执行文件。
kill 终止正在调试的程序。
list 执行一行源代码但不进入函数内部。
next 执行一行源代码但不进入函数内部。
step执行一行源代码而且进入函数内部。
run执行当前被调试的程序
quit 终止 gdb
watch 使你能监视一个变量的值而不管它何时被改变。
break 在代码里设置断点, 这将使程序执行到这里时被挂起。
make 使你能不退出 gdb 就可以重新产生可执行文件。
shell 使你能不离开 gdb 就执行 UNIX shell 命令。
gdb 支持很多与 UNIX shell 程序一样的命令编辑特征。 你能象在 bash 或 tcsh里那样按 Tab 键让 gdb 帮你补齐一个唯一的命令, 如果不唯一的话 gdb 会列出所有匹配的命令。 你也能用光标键上下翻动历史命令。
gdb 应用举例
本节用一个实例教你一步步的用 gdb 调试程序。 被调试的程序相当的简单, 但它展示了 gdb 的典型应用。
下面列出了将被调试的程序。 这个程序被称为 greeting , 它显示一个简单的问候, 再用反序将它列出。
#include
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
void my_print (char *string)
{
printf ("The string is %sn", string);
}
void my_print2 (char *string)
{
char *string2;
int size, i;
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - i] = string[i];
string2[size+1] = `';
printf ("The string printed backward is %sn", string2);
}
用下面的命令编译它:
gcc -o test test.c
这个程序执行时显示如下结果:
The string is hello there
The string printed backward is
输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的。 我们所设想的输出应该是:
The string printed backward is ereht olleh
由于某些原因, my_print2 函数没有正常工作。 让我们用 gdb 看看问题究竟出在哪儿, 先键入如下命令:
gdb greeting
注意: 记得在编译 greeting 程序时把调试选项打开。
如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用 file 命令来载入它:
(gdb) file greeting
这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样。
这时你能用 gdb 的 run 命令来运行 greeting 了。 当它在 gdb 里被运行后结果大约会象这样:
(gdb) run
Starting program: /root/greeting
The string is hello there
The string printed backward is
Program exited with code 041
这个输出和在 gdb 外面运行的结果一样。 问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码:
(gdb) list
(gdb) list
(gdb) list
技巧: 在 gdb 提示符下按回车健将重复上一个命令。
第一次键入 list 命令的输出如下:
1 #include
2
3 main ()
4 {
5 char my_string[] = "hello there";
6
7 my_print (my_string);
8 my_print2 (my_string);
9 }
10
如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:
11 my_print (char *string)
12 {
13 printf ("The string is %sn", string);
14 }
15
16 my_print2 (char *string)
17 {
18 char *string2;
19 int size, i;
20
再按一次回车将列出 greeting 程序的剩余部分:
21 size = strlen (string);
22 string2 = (char *) malloc (size + 1);
23 for (i = 0; i < size; i++)
24 string2[size - i] = string[i];
25 string2[size+1] = `';
26 printf ("The string printed backward is %sn", string2);
27 }
根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点:
(gdb) break 24
gdb 将作出如下的响应:
Breakpoint 1 at 0x139: file greeting.c, line 24
(gdb)
现在再键入 run 命令, 将产生如下的输出:
Starting program: /root/greeting
The string is hello there
Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24
24 string2[size-i]=string[i]
你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入:
(gdb) watch string2[size - i]
gdb 将作出如下回应:
Watchpoint 2: string2[size - i]
现在可以用 next 命令来一步步的执行 for 循环了:
(gdb) next
经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`。 gdb 用如下的显示来告诉你这个信息:
Watchpoint 2, string2[size - i]
Old value = 0 `00'
New value = 104 `h'
my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23
23 for (i=0; i
这个值正是期望的。 后来的数次循环的结果都是正确的。 当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size - i 的值等于 1, 最后一个字符已经拷到新串里了。
如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符。 所以 string2 的第一个字符是空字符。 这解释了为什么在打印 string2 时没有任何输出了。
现在找出了问题出在哪里, 修正这个错误是很容易的。 你得把代码里写入 string2 的第一个字符的的偏移量改为 size - 1 而不是 size。 这是因为 string2 的大小为 12, 但起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留。
为了使代码正常工作有很多种修改办法。 一种是另设一个比串的实际大小小 1 的变量。 这是这种解决办法的代码:
#include
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
my_print (char *string)
{
printf ("The string is %sn", string);
}
my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = `';
printf ("The string printed backward is %sn", string2);
}
另外的 C 编程工具
Slackware Linux 的发行版中还包括一些我们尚未提到的 C 开发工具。 本节将介绍这些工具和它们的典型用法。
xxgdb
xxgdb 是 gdb 的一个基于 X Window 系统的图形界面。 xxgdb 包括了命令行版的 gdb 上的所有特性。 xxgdb 使你能通过按按钮来执行常用的命令。 设置了断点的地方也用图形来显示。
你能在一个 Xterm 窗口里键入下面的命令来运行它:
xxgdb
你能用 gdb 里任何有效的命令行选项来初始化 xxgdb 。 此外 xxgdb 也有一些特有的命令行选项, 表 27.2 列出了这些选项。
表 27.2. xxgdb 命令行选项.
选 项 描 述
db_name 指定所用调试器的名字, 缺省是 gdb。
db_prompt 指定调试器提示符, 缺省为 gdb。
gdbinit 指定初始化 gdb 的命令文件的文件名, 缺省为 .gdbinit。
nx 告诉 xxgdb 不执行 .gdbinit 文件。
bigicon 使用大图标。
calls
你可以在 sunsite.unc.edu FTP 站点用下面的路径:
/pub/Linux/devel/lang/c/calls.tar.Z
来取得 calls , 一些旧版本的 Linux CD-ROM 发行版里也附带有。 因为它是一个有用的工具, 我们在这里也介绍一下。 如果你觉得有用的话, 从 BBS, FTP, 或另一张CD-ROM 上弄一个拷贝。 calls 调用 GCC 的预处理器来处理给出的源程序文件, 然后输出这些文件的里的函数调用树图。
注意: 在你的系统上安装 calls , 以超级用户身份登录后执行下面的步骤: 1. 解压和 untar 文件。 2.cd 进入 calls untar 后建立的子目录。 3.把名叫 calls 的文件移动到 /usr/bin 目录。 4.把名叫 calls.1 的文件移动到目录 /usr/man/man1 。 5.删除 /tmp/calls 目录。 这些步骤将把 calls 程序和它的指南页安装载你的系统上。
当 calls 打印出调用跟踪结果时, 它在函数后面用中括号给出了函数所在文件的文件名:
main [test.c]
如果函数并不是向 calls 给出的文件里的, calls 不知道所调用的函数来自哪里, 则只显示函数的名字:
printf
calls 不对递归和静态函数输出。 递归函数显示成下面的样子:
fact <<< recursive in factorial.c >>>
静态函数象这样显示:
total [static in calculate.c]
作为一个例子, 假设用 calls 处理下面的程序:
#include
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2(my_string);
}
my_print (char *string)
{
printf ("The string is %sn", string);
}
my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = `';
printf ("The string printed backward is %sn", string2);
}
将产生如下的输出:
1 main [test.c]
2 my_print [test.c]
3 printf
4 my_print2 [test.c]
5 strlen
6 malloc
7 printf
calls 有很多命令行选项来设置不同的输出格式, 有关这些选项的更多信息请参考 calls 的指南页。 方法是在命令行上键入 calls -h 。
cproto
cproto 读入 C 源程序文件并自动为每个函数产生原型申明。 用 cproto 可以在写程序时为你节省大量用来定义函数原型的时间。
如果你让 cproto 处理下面的代码:
#include
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2(my_string);
}
my_print (char *string)
{
printf ("The string is %sn", *string);
}
my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = `';
printf ("The string printed backward is %sn", string2);
}
你将得到下面的输出:
/* test.c */
int main(void);
int my_print(char *string);
int my_print2(char *string);
这个输出可以重定向到一个定义函数原型的包含文件里。
indent
indent 实用程序是 Linux 里包含的另一个编程实用工具。 这个工具简单的说就为你的代码产生美观的缩进的格式。 indent 也有很多选项来指定如何格式化你的源代码。这些选项的更多信息请看indent 的指南页, 在命令行上键入 indent -h 。
下面的例子是 indent 的缺省输出:
运行 indent 以前的 C 代码:
#include
main () {
char my_string[] = "hello there";
my_print (my_string);
my_print2(my_string); }
my_print (char *string)
{
printf ("The string is %sn", *string);
}
my_print2 (char *string) {
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = `';
printf ("The string printed backward is %sn", string2);
}
运行 indent 后的 C 代码:
#include
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
my_print (char *string)
{
printf ("The string is %sn", *string);
my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = `';
printf ("The string printed backward is %sn", string2);
}
indent 并不改变代码的实质内容, 而只是改变代码的外观。 使它变得更可读, 这永远是一件好事。
gprof
gprof 是安装在你的 Linux 系统的 /usr/bin 目录下的一个程序。 它使你能剖析你的程序从而知道程序的哪一个部分在执行时最费时间。
gprof 将告诉你程序里每个函数被调用的次数和每个函数执行时所占时间的百分比。 你如果想提高你的程序性能的话这些信息非常有用。
为了在你的程序上使用 gprof, 你必须在编译程序时加上 -pg 选项。 这将使程序在每次执行时产生一个叫 gmon.out 的文件。 gprof 用这个文件产生剖析信息。
在你运行了你的程序并产生了 gmon.out 文件后你能用下面的命令获得剖析信息:
gprof
参数 program_name 是产生 gmon.out 文件的程序的名字。
技巧: gprof 产生的剖析数据很大, 如果你想检查这些数据的话最好把输出重定向到一个文件里。
f2c 和 p2c
f2c 和 p2c 是两个源代码转换程序。 f2c 把 FORTRAN 代码转换为 C 代码, p2c 把 Pascal 代码转换为 C 代码。 当你安装 GCC 时这两个程序都会被安装上去。
如果你有一些用 FORTRAN 或 Pascal 写的代码要用 C 重写的话, f2c 和 p2c 对你非常有用。 这两个程序产生的 C 代码一般不用修改就直接能被 GCC 编译。
如果要转换的 FORTRAN 或 Pascal 程序比较小的话可以直接使用 f2c 或 p2c 不用加任何选项。 如果要转换的程序比较庞大, 包含很多文件的话你可能要用到一些命令行选项。
在一个 FORTRAN 程序上使用 f2c , 输入下面的命令:
f2c my_fortranprog.f
注意: f2c 要求被转换的程序的扩展名为 .f 或 a .F 。
要把一个Pascal 程序装换为 C 程序, 输入下面的命令:
p2c my_pascalprogram.pas
这两个程序产生的 C 源代码的文件名都和原来的文件名相同, 但扩展名由 .f 或 .pas 变为 .c 。


Linux下C语言编程基础(Makefile)

发表人:upczap | 发表时间: 2007年三月26日, 22:47

Linux下C语言编程基础(Makefile)

假设我们有下面这样的一个程序,源代码如下:

/* main.c */ 

#include "mytool1.h" 

#include "mytool2.h" 

int main(int argc,char **argv) 

{ 

mytool1_print("hello"); 

mytool2_print("hello"); 

} 

/* mytool1.h */ 

#ifndef _MYTOOL_1_H 

#define _MYTOOL_1_H 

void mytool1_print(char *print_str); 

#endif 

/* mytool1.c */ 

#include "mytool1.h" 

void mytool1_print(char *print_str) 

{ 

printf("This is mytool1 print %s ",print_str); 

} 

/* mytool2.h */ 

#ifndef _MYTOOL_2_H 

#define _MYTOOL_2_H 

void mytool2_print(char *print_str); 

#endif 

/* mytool2.c */ 

#include "mytool2.h" 

void mytool2_print(char *print_str) 

{ 

printf("This is mytool2 print %s ",print_str); 

}

当然由于这个程序很短,我们可以这样来编译:

gcc -c main.c 

gcc -c mytool1.c 

gcc -c mytool2.c 

gcc -o main main.o mytool1.o mytool2.o

这样的话我们也可以产生main程序,而且也不是很麻烦。但是如果我们考虑一下如果有一天我们修改了其中的一个文件(比如说mytool1.c)那么我们难道还要重新输入上面的命令?也许你会说,这个很容易解决啊,我写一个SHELL脚本,让它帮我去完成不就可以了。是的对于这个程序来说,是可以起到作用的。但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译?

为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make。我们只要执行以下make,就可以把上面的问题解决掉。在我们执行make之前,我们要先编写一个非常重要的文件。--Makefile。对于上面的那个程序来说,可能的一个Makefile的文件是:

# 这是上面那个程序的Makefile文件:

main:main.o mytool1.o mytool2.o 

gcc -o main main.o mytool1.o mytool2.o 

main.o:main.c mytool1.h mytool2.h 

gcc -c main.c 

mytool1.o:mytool1.c mytool1.h 

gcc -c mytool1.c 

mytool2.o:mytool2.c mytool2.h 

gcc -c mytool2.c

有了这个Makefile文件,不论我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件它连理都不想去理的。

下面我们学习Makefile是如何编写的。

在Makefile中也#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说明。一般的格式是:

target:components

TAB rule

第一行表示的是依赖关系。第二行是规则。

比如说我们上面的那个Makefile文件的第二行。

main:main.o mytool1.o mytool2.o

表示我们的目标(target)main的依赖对象(components)是main.o mytool1.omytool2.o 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令。就象我们的上面那个Makefile第三行所说的一样要执行 gcc-o main main.o mytool1.o mytool2.o 注意规则一行中的TAB表示那里是一个TAB键

Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是:

$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。

如果我们使用上面三个变量,那么我们可以简化我们的Makefile文件为:

# 这是简化后的Makefile

main:main.o mytool1.o mytool2.o

gcc -o $@ $^

main.o:main.c mytool1.h mytool2.h

gcc -c $<

mytool1.o:mytool1.c mytool1.h

gcc -c $<

mytool2.o:mytool2.c mytool2.h

gcc -c $<

经过简化后,我们的Makefile是简单了一点,不过人们有时候还想简单一点。这里我们学习一个Makefile的缺省规则

.c.o:

gcc -c $<

这个规则表示所有的 .o文件都是依赖与相应的.c文件的。例如mytool.o依赖于mytool.c这样Makefile还可以变为:

# 这是再一次简化后的Makefile

main:main.o mytool1.o mytool2.o

gcc -o $@ $^

.c.o:

gcc -c $<

好了,我们的Makefile 也差不多了,如果想知道更多的关于Makefile的规则,可以查看相应的文档。


Valid XHTML 1.0 Strict and CSS. Powered by pLog
Design by Blog.lvwo.com