显示标签为“C”的博文。显示所有博文
显示标签为“C”的博文。显示所有博文

2007年9月20日星期四

C/C++中判断某一文件或目录是否存在

C/C++中判断某一文件或目录是否存在
1.C++很简单的一种办法:
#include <iostream>
#include
<fstream>
using namespace std;
#define FILENAME "stat.dat"
int main()
{
fstream _file;
_file.open(FILENAME,ios::
in);
if(!_file)
{
cout
<<FILENAME<<"没有被创建";
}
else
{
cout
<<FILENAME<<"已经存在";
}
return 0;
}

2.利用 c 语言的库的办法:

函数名: access
功 能: 确定文件的访问权限
用 法: int access(const char *filename, int amode);
以前一直没用过这个函数,今天调试程序发现了这个函数,感觉挺好用,尤其是判断一个文件或文件夹是否存在的时候,用不着再find了,文件的话还可以检测读写权限,文件夹的话则只能判断是否存在,下面摘自MSDN:

int _access( const char *path, int mode );

Return Value

Each of these functions returns 0 if the file has the given mode. The function returns –1 if the named file does not exist or is not accessible in the given mode; in this case, errno is set as follows:

EACCES

Access denied: file’s permission setting does not allow specified access.

ENOENT

Filename or path not found.

Parameters

path

File or directory path

mode

Permission setting

Remarks

When used with files, the _access function determines whether the specified file exists and can be accessed as specified by the value of mode. When used with directories, _access determines only whether the specified directory exists; in Windows NT, all directories have read and write access.

mode Value Checks File For
00 Existence only
02 Write permission
04 Read permission
06 Read and write permission

Example

/* ACCESS.C: This example uses _access to check the
* file named "ACCESS.C" to see if it exists and if
* writing is allowed.
*/

#include
<io.h>
#include
<stdio.h>
#include
<stdlib.h>

void main( void )
{
/* Check for existence */
if( (_access( "ACCESS.C", 0 )) != -1 )
{
printf(
"File ACCESS.C exists " );
/* Check for write permission */
if( (_access( "ACCESS.C", 2 )) != -1 )
printf(
"File ACCESS.C has write permission " );
}
}
Output
File ACCESS.C existsFile ACCESS.C has write permission

3.在windows平台下用API函数FindFirstFile(...):

(1)检查文件是否存在:

#define _WIN32_WINNT 0x0400

#include
"windows.h"

int
main(
int argc, char *argv[])
{
WIN32_FIND_DATA FindFileData;
HANDLE hFind;

printf (
"Target file is %s. ", argv[1]);

hFind
= FindFirstFile(argv[1], &FindFileData);

if (hFind == INVALID_HANDLE_VALUE) {
printf (
"Invalid File Handle. Get Last Error reports %d ", GetLastError ());
}
else {
printf (
"The first file found is %s ", FindFileData.cFileName);
FindClose(hFind);
}

return (0);
}

(2)检查某一目录是否存在:

///目录是否存在的检查:
bool CheckFolderExist(const string &strPath)
{
WIN32_FIND_DATA wfd;
bool rValue = false;
HANDLE hFind
= FindFirstFile(strPath.c_str(), &wfd);
if ((hFind != INVALID_HANDLE_VALUE) && (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
rValue
= true;
}
FindClose(hFind);
return rValue;
}

4.使用boost的filesystem类库的exists函数

#include <boost/filesystem/operations.hpp>
#include
<boost/filesystem/path.hpp>
#include
<boost/filesystem/convenience.hpp>

int GetFilePath(std::string &strFilePath)
{
string strPath;
int nRes = 0;

//指定路径
strPath = "D:\myTest\Test1\Test2";
namespace fs = boost::filesystem;

//路径的可移植
fs::path full_path( fs::initial_path() );
full_path
= fs::system_complete( fs::path(strPath, fs::native ) );
//判断各级子目录是否存在,不存在则需要创建
if ( !fs::exists( full_path ) )
{
// 创建多层子目录
bool bRet = fs::create_directories(full_path);
if (false == bRet)
{
return -1;
}

}
strFilePath
= full_path.native_directory_string();

return 0;
}


利用c++标准库函数实现判断文件存在 CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

利用c++标准库函数实现判断文件存在 CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。
在c++标准库函数中,没有直接判断文件存不存在的函数。而为了这一个小小的功能要实现操作系统代码分开很是划不来。
在偶然的机会中,发现了一个很简单的好方法可以实现这个功能。只利用标准库函数而不用写操作系统相关代码。
在c++标准库中,有一个文件操作函数,rename用于文件改名,其位于cstdio中。rename接受new,old两个char*参数,返回改名操作结果。我们可以利用改名功能,将其两个参数都设置成要判断的文件名,而返回值可能会是我们需要的结果。
rename返回0表示正常,返回非0表示有错误。而这样实现的exist函数,同样,返回0表示存在,非0表示不存在。还可以根据需要,将0所对应结果反过来。
最后的实现就是return !rename(filename, filename)。

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=916628

检测文件存在的四种方法。test if a File exist or not?

检测文件存在的四种方法。test if a File exist or not?

1.
WIN32_FIND_DATA m_data;
HANDLE hFile;

hFile=FindFirstFile(filename,&m_data)

if(hFile==INVALID_HANDLE_VALUE) //file not found

Make sure you close the handle if the file is found.

FindClose(hFile);

2.
You can use SHGetFileInfo()
The prototype of the function is as follows:

DWORD_PTR SHGetFileInfo(
LPCTSTR pszPath,
DWORD dwFileAttributes,
SHFILEINFO *psfi,
UINT cbFileInfo,
UINT uFlags
);


3.
PathFileExists

4. 加一个标准c库函数

Example

/* ACCESS.C: This example uses _access to check the
* file named "ACCESS.C" to see if it exists and if
* writing is allowed.
*/

#include
#include
#include

void main( void )
{
/* Check for existence */
if( (_access( "ACCESS.C", 0 )) != -1 )
{
printf( "File ACCESS.C exists\n" );
/* Check for write permission */
if( (_access( "ACCESS.C", 2 )) != -1 )
printf( "File ACCESS.C has write permission\n" );
}
}

2007年9月19日星期三

使用C语言的函数rename()来进行文件的move

呵呵,这个函数就等于我们Linux下的mv命令。其man页如下:

RENAME(2) Linux Programmer's Manual RENAME(2)

NAME
rename - change the name or location of a file

SYNOPSIS
#include

int rename(const char *oldpath, const char *newpath);

DESCRIPTION
rename renames a file, moving it between directories if required.

Any other hard links to the file (as created using link(2)) are unaf-
fected.

If newpath already exists it will be atomically replaced (subject to a
few conditions - see ERRORS below), so that there is no point at which
another process attempting to access newpath will find it missing.

If newpath exists but the operation fails for some reason rename guar-
antees to leave an instance of newpath in place.

However, when overwriting there will probably be a window in which both
oldpath and newpath refer to the file being renamed.

If oldpath refers to a symbolic link the link is renamed; if newpath
refers to a symbolic link the link will be overwritten.

RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is
set appropriately.

ERRORS
EISDIR newpath is an existing directory, but oldpath is not a direc-
tory.

EXDEV oldpath and newpath are not on the same filesystem.

ENOTEMPTY or EEXIST
newpath is a non-empty directory, i.e., contains entries other
than "." and "..".

EBUSY The rename fails because oldpath or newpath is a directory that
is in use by some process (perhaps as current working directory,
or as root directory, or because it was open for reading) or is
in use by the system (for example as mount point), while the
system considers this an error. (Note that there is no require-
ment to return EBUSY in such cases - there is nothing wrong with
doing the rename anyway - but it is allowed to return EBUSY if
the system cannot otherwise handle such situations.)

EINVAL The new pathname contained a path prefix of the old, or, more
generally, an attempt was made to make a directory a subdirec-
tory of itself.
EMLINK oldpath already has the maximum number of links to it, or it was
a directory and the directory containing newpath has the maximum
number of links.

ENOTDIR
A component used as a directory in oldpath or newpath is not, in
fact, a directory. Or, oldpath is a directory, and newpath
exists but is not a directory.

EFAULT oldpath or newpath points outside your accessible address space.

EACCES Write access to the directory containing oldpath or newpath is
not allowed for the process's effective uid, or one of the
directories in oldpath or newpath did not allow search (execute)
permission, or oldpath was a directory and did not allow write
permission (needed to update the .. entry).

EPERM or EACCES
The directory containing oldpath has the sticky bit set and the
process's effective uid is neither that of root nor the uid of
the file to be deleted nor that of the directory containing it,
or newpath is an existing file and the directory containing it
has the sticky bit set and the process's effective uid is nei-
ther that of root nor the uid of the file to be replaced nor
that of the directory containing it, or the filesystem contain-
ing pathname does not support renaming of the type requested.

ENAMETOOLONG
oldpath or newpath was too long.

ENOENT A directory component in oldpath or newpath does not exist or
is a dangling symbolic link.

ENOMEM Insufficient kernel memory was available.

EROFS The file is on a read-only filesystem.

ELOOP Too many symbolic links were encountered in resolving oldpath or
newpath.

ENOSPC The device containing the file has no room for the new directory
entry.

CONFORMING TO
POSIX, 4.3BSD, ANSI C

BUGS
On NFS filesystems, you can not assume that if the operation failed the
file was not renamed. If the server does the rename operation and then
crashes, the retransmitted RPC which will be processed when the server
is up again causes a failure. The application is expected to deal with
this. See link(2) for a similar problem.

SEE ALSO
link(2), unlink(2), symlink(2), mv(1)

Linux 2.0 1998-06-04 RENAME(2)
我们先来试验下Win32下的效果,代码如下:


呵呵,在移动的目的地不需创建新文件夹的环境下工作良好!

如果没有对应的目录的话,会出错!!
Win32下无法用该函数给目录改名!!

2007年9月18日星期二

关于C结构体bit field

关于C结构体bit field

C语言的STRUCT提供了一种叫bit field的语法,可以根据需要决定成员占用某字节的从X位到Y位,例如,下面一个结构:
struct tagtest
{
char a:4;
char b:2;
char c:2;
};

这个定义的含义是整个结构是一个字节长度,成员a占4位,b占2位,c占2位。这样定义以后,我们可以方便的通过设置成员的值来设置结构,而不需要进行位操作了。例如:
tagtest myTest;
myTest.a = 10;
myTest.b = 2;
myTest.c = 1;

但今天发现一个问题,就是windows系统上的和MAC上对待这个结构是不同的;现象如下:
如果在windows上这是上面的值,在MAC上得到的结构成员值为:
myTest.a = 6;myTest.b = 2;myTest.c = 2;
仔细分析之后觉得这个不是字节序的问题,因为字节序对一个字节是不起作用的,如果起作用那传输数据就麻烦了了;那么是什么问题导致的呢?
应该是编译器造成的,规律如下:
在WINDOS上,编译器认为c是字节的高位,而a是字节的低位;但MAC上正好相反了;a 是字节的高位,c是字节的低位。
紧记在心!!!

c程序中使用exec()和system()那个更好

c程序中使用exec()和system()那个更好 这两个函数具体在使用中那个更好
更可靠更安全呢

system()自己会fork(),exec()...
system跟exec功能都不同
当进程调用一种e x e c函数时,该进程完全由新程序代换,而新程序则从其m a i n函数开始执行。
因为调用e x e c并不创建新进程,所以前后的进程I D并未改变。e x e c只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。

说句废话:具体问题具体分析。
能具体说说吗?
system跟exec功能都不同
当进程调用一种e x e c函数时,该进程完全由新程序代换,而新程序则从其m a i n函数开始执行。
因为调用e x e c并不创建新进程,所以前后的进程I D并未改变。e x e c只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。

明白了
多谢啊

system posix2.0 定义ignore SIGINT SIGQUIT.
./glibc-2.3.5/sysdeps/posix/system.c

先查下再来问问题吧,呵呵
system太倚赖于shell.不过具体情况应具体对待.

va_list

va_list
2007-04-28 13:47

本文主要介绍可变参数的函数使用,然后分析它的原理,程序员自己如何对它们实现和封装,最后是可能会出现的问题和避免措施。
VA函数(variable argument function),参数个数可变函数,又称可变参数函数。C/C++编程中,系统提供给编程人员的va函数很少。*printf()/*scanf() 系列函数,用于输入输出时格式化字符串;exec*()系列函数,用于在程序中执行外部文件(main(int argc, char* argv[]算不算呢,与其说main()也是一个可变参数函数,倒不如说它是exec*()经过封装后的具备特殊功能和意义的函数,至少在原理这一级上 有很多相似之处)。由于参数个数的不确定,使va函数具有很大的灵活性,易用性,对没有使用过可变参数函数的编程人员很有诱惑力;那么,该如何编写自己的 va函数,va函数的运用时机、编译实现又是如何。作者借本文谈谈自己关于va函数的一些浅见。

一、 从printf()开始

从大家都很熟悉的格式化字符串函数开始介绍可变参数函数。

原型:int printf(const char * format, ...);

参数format表示如何来格式字符串的指令,…

表示可选参数,调用时传递给"..."的参数可有可无,根据实际情况而定。

系统提供了vprintf系列格式化字符串的函数,用于编程人员封装自己的I/O函数。

int vprintf / vscanf(const char * format, va_list ap); // 从标准输入/输出格式化字符串
int vfprintf / vfsacanf(FILE * stream, const char * format, va_list ap); // 从文件流
int vsprintf / vsscanf(char * s, const char * format, va_list ap); // 从字符串

// 例1:格式化到一个文件流,可用于日志文件


FILE *logfile;
int WriteLog(const char * format, ...)
{
va_list arg_ptr;

va_start(arg_ptr, format);
int nWrittenBytes = vfprintf(logfile, format, arg_ptr);
va_end(arg_ptr);

return nWrittenBytes;
}

// 调用时,与使用printf()没有区别。
WriteLog("%04d-%02d-%02d %02d:%02d:%02d %s/%04d logged out.",
nYear, nMonth, nDay, nHour, nMinute, szUserName, nUserID);


同理,也可以从文件中执行格式化输入;或者对标准输入输出,字符串执行格式化。

在上面的例1中,WriteLog()函数可以接受参数个数可变的输入,本质上,它的实现需要vprintf()的支持。如何真正实现属于自己的可变参数函数,包括控制每一个传入的可选参数。

二、 va函数的定义和va宏

C语言支持va函数,作为C语言的扩展--C++同样支持va函数,但在C++中并不推荐使用,C++引入的多态性同样可以实现参数个数可变的函 数。不过,C++的重载功能毕竟只能是有限多个可以预见的参数个数。比较而言,C中的va函数则可以定义无穷多个相当于C++的重载函数,这方面C++是 无能为力的。va函数的优势表现在使用的方便性和易用性上,可以使代码更简洁。C编译器为了统一在不同的硬件架构、硬件平台上的实现,和增加代码的可移植 性,提供了一系列宏来屏蔽硬件环境不同带来的差异。

ANSI C标准下,va的宏定义在stdarg.h中,它们有:va_list,va_start(),va_arg(),va_end()。

// 例2:求任意个自然数的平方和:


int SqSum(int n1, ...)
{
va_list arg_ptr;
int nSqSum = 0, n = n1;

va_start(arg_ptr, n1);
while (n > 0)
{
nSqSum += (n * n);
n = va_arg(arg_ptr, int);
}
va_end(arg_ptr);

return nSqSum;
}

// 调用时
int nSqSum = SqSum(7, 2, 7, 11, -1);


可变参数函数的原型声明格式为:

type VAFunction(type arg1, type arg2, … );

参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用"…"表示。固定参数和可选参数公同构成一个函数的参数列表。

借助上面这个简单的例2,来看看各个va_xxx的作用。
va_list arg_ptr:定义一个指向个数可变的参数列表指针;

va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个 固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c)。

va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。

va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。

va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用 va_start()、va_copy()恢复arg_ptr。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内。


三、 编译器如何实现va

例2中调用SqSum(7, 2, 7, 11, -1)来求7, 2, 7, 11的平方和,-1是结束标志。

简单地说,va函数的实现就是对参数指针的使用和控制。


typedef char * va_list; // x86平台下va_list的定义


函数的固定参数部分,可以直接从函数定义时的参数名获得;对于可选参数部分,先将指针指向第一个可选参数,然后依次后移指针,根据与结束标志的比较来判断是否已经获得全部参数。因此,va函数中结束标志必须事先约定好,否则,指针会指向无效的内存地址,导致出错。

这里,移动指针使其指向下一个参数,那么移动指针时的偏移量是多少呢,没有具体答案,因为这里涉及到内存对齐(alignment)问题,内存对齐 跟具体使用的硬件平台有密切关系,比如大家熟知的32位x86平台规定所有的变量地址必须是4的倍数(sizeof(int) = 4)。va机制中用宏_INTSIZEOF(n)来解决这个问题,没有这些宏,va的可移植性无从谈起。

首先介绍宏_INTSIZEOF(n),它求出变量占用内存空间的大小,是va的实现的基础。


#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )


#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //第一个可选参数地址
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
#define va_end(ap) ( ap = (va_list)0 ) // 将指针置为无效


下表是针对函数int TestFunc(int n1, int n2, int n3, …) 参数传递时的内存堆栈情况。(C编译器默认的参数传递方式是__cdecl。)

对该函数的调用为int result = TestFunc(a, b, c, d. e); 其中e为结束标志。

从上图中可以很清楚地看出

va_xxx宏如此编写的原因。

1. va_start。为了得到第一个可选参数的地址,我们有三种办法可以做到:

A) = &n3 + _INTSIZEOF(n3)
// 最后一个固定参数的地址 + 该参数占用内存的大小

B) = &n2 + _INTSIZEOF(n3) + _INTSIZEOF(n2)
// 中间某个固定参数的地址 + 该参数之后所有固定参数占用的内存大小之和

C) = &n1 + _INTSIZEOF(n3) + _INTSIZEOF(n2) + _INTSIZEOF(n1)
// 第一个固定参数的地址 + 所有固定参数占用的内存大小之和

从编译器实现角度来看,方法B),方法C)为了求出地址,编译器还需知道有多少个固定参数,以及它们的大小,没有把问题分解到最简单,所以不是很聪 明的途径,不予采纳;相对来说,方法A)中运算的两个值则完全可以确定。va_start()正是采用A)方法,接受最后一个固定参数。调用 va_start()的结果总是使指针指向下一个参数的地址,并把它作为第一个可选参数。在含多个固定参数的函数中,调用va_start()时,如果不 是用最后一个固定参数,对于编译器来说,可选参数的个数已经增加,将给程序带来一些意想不到的错误。(当然如果你认为自己对指针已经知根知底,游刃有余, 那么,怎么用就随你,你甚至可以用它完成一些很优秀(高效)的代码,但是,这样会大大降低代码的可读性。)

注意:宏va_start是对参数的地址进行操作的,要求参数地址必须是有效的。一些地址无效的类型不能当作固定参数类型。比如:寄存器类型,它的地址不是有效的内存地址值;数组和函数也不允许,他们的长度是个问题。因此,这些类型时不能作为va函数的参数的。

2. va_arg身兼二职:返回当前参数,并使参数指针指向下一个参数。

初看va_arg宏定义很别扭,如果把它拆成两个语句,可以很清楚地看出它完成的两个职责。


#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
// 将( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )拆成:
/* 指针ap指向下一个参数的地址 */
1. ap += _INTSIZEOF(t); // 当前,ap已经指向下一个参数了
/* ap减去当前参数的大小得到当前参数的地址,再强制类型转换后返回它的值 */
2. return *(t *)( ap - _INTSIZEOF(t))


回想到printf/scanf系列函数的%d %s之类的格式化指令,我们不难理解这些它们的用途了- 明示参数强制转换的类型。

(注:printf/scanf没有使用va_xxx来实现,但原理是一致的。)

3.va_end很简单,仅仅是把指针作废而已。

#define va_end(ap) (ap = (va_list)0) // x86平台

四、 简洁、灵活,也有危险

从va的实现可以看出,指针的合理运用,把C语言简洁、灵活的特性表现得淋漓尽致,叫人不得不佩服C的强大和高效。不可否认的是,给编程人员太多自由空间必然使程序的安全性降低。va中,为了得到所有传递给函数的参数,需要用va_arg依次遍历。其中存在两个隐患:

1)如何确定参数的类型。 va_arg在类型检查方面与其说非常灵活,不如说是很不负责,因为是强制类型转换,va_arg都把当前指针所指向的内容强制转换到指定类型;

2)结束标志。如果没有结束标志的判断,va将按默认类型依次返回内存中的内容,直到访问到非法内存而出错退出。例2中SqSum()求的是自然数 的平方和,所以我把负数和0作为它的结束标志。例如scanf把接收到的回车符作为结束标志,大家熟知的printf()对字符串的处理用'\0'作为结 束标志,无法想象C中的字符串如果没有'\0', 代码将会是怎样一番情景,估计那时最流行的可能是字符数组,或者是malloc/free。

允许对内存的随意访问,会留给不怀好意者留下攻击的可能。当处理cracker精心设计好的一串字符串后,程序将跳转到一些恶意代码区域执行,以使cracker达到其攻击目的。(常见的exploit攻击)所以,必需禁止对内存的随意访问和严格控制内存访问边界。

五、 Unix System V兼容方式的va声明

上面介绍可变参数函数的声明是采用ANSI标准的,Unix System V兼容方式的声明有一点点区别,它增加了两个宏:va_alist,va_dcl。而且它们不是定义在stdarg.h中,而是varargs.h中。 stdarg.h是ANSI标准的;varargs.h仅仅是为了能与以前的程序保持兼容而出现的,现在的编程中不推荐使用。

va_alist:函数声明/定义时出现在函数头,用以接受参数列表。

va_dcl:对va_alist的声明,其后无需跟分号";"

va_start的定义也不相同。因为System V可变参数函数声明不区分固定参数和可选参数,直接对参数列表操作。所以va_start()不是va_start(ap,v),而是简化为va_start(ap)。其中,ap是va_list型的参数指针。

Unix System V兼容方式下函数的声明形式:

type VAFunction(va_alist)
va_dcl // 这里无需分号
{
// 函数体内同ANSI标准
}// 例3:猜测execl的实现(Unix System V兼容方式),摘自SUS V2


#include

#define MAXARGS 100
/ * execl(file, arg1, arg2, ..., (char *)0); */

execl(va_alist)
va_dcl
{
va_list ap;
char *file;
char *args[MAXARGS];
int argno = 0;

va_start(ap);
file = va_arg(ap, char *);
while ((args[argno++] = va_arg(ap, char *)) != (char *)0)
;
va_end(ap);
return execv(file, args);
}

六、 扩展与思考

个数可变参数在声明时只需"..."即可;但是,我们在接受这些参数时不能"..."。va函数实现的关键就是如何得到参数列表中可选参数,包括参 数的值和类型。以上的所有实现都是基于来自stdarg.h的va_xxx的宏定义。 <思考>能不能不借助于va_xxx,自己实现VA呢?,我想到的方法是汇编。在C中,我们当然就用C的嵌入汇编来实现,这应该是可以做得到 的。至于能做到什么程度,稳定性和效率怎么样,主要要看你对内存和指针的控制了。

2007年9月6日星期四

C、C++ 文件系统操作(改名、删除等)

删除文件我在windows下面用了_unlink()函数,在PPC上也编译通过,我就没当回事情。没想到在Linux的gcc上又编译不通过。。。

今天在网上找了好多次文件删除等操作的方法,始终没看到和我心意,能和平台无关的。最后苍天不负有心人啊,还是让我找到了 :)
look:
int remove ( const char * filename );
int rename ( const char * oldname, const char * newname );
恩,也就这两个了:)

文件复制嘛,可以看我的

如何用c++的流操作复制文件

呵呵,开心好多

不过这里要尤其注意的是,Windows和Linux的分隔符不同!!
Windows: \
Linux: /

我就因此而犯错。。。

呵呵,不能再犯啦!!!

没有size_t的定义?

竟然PPC下的gcc没出现这个问题,倒是让Linux下的GCC给碰上了,这年头真是,什么事情都有。。。

这个弱智问题,网上想找答案还真的不简单。。。最后千辛万苦还是让我找到了 :)
参见 http://archive.gingerall.cz/archives/public/sablot2002/msg01354.html ,这位朋友在HP-UX上编译也碰到这个问题。最后还好,我也不知道怎么有幸看到了答案:


Hi Pavel

By including or in shandler.h the compile went
fine.

A 'more' on _size_t.h says:

/************************************************
** File:
**
** Description:
** This file is the definitive declaration of
** the size_t type. This file should be included
** by all other header files if they need size_t
** defined.
**
** Warning: The calling header file is responsible
** for determining if size_t can be included
** without polluting namespace.
**************************************************/

HTH

Frank

_____________________________________________________________________
There is this suble bug in how the files are included, I'm hunting it
for a while. I'd really appreciate, if you could help me to solve this
problem.

On some platforms there is some include needed for the size_t type.
Please, try to find, which one is it for your platfrom. I'll try to fix
the configuration, what is unfortunatelly a bit tricky, since the
interface headers (and shandler.h is one of them) must not depend on the
configuration (config.h or platform.h - both are Sablotron files).

So try to hack some include into shandler.h, or change the size_t
identifier to 'unsigned long'. Both of it should help you, the first
choice could help me too. :)

HTH

Pavel

Frank Limstrand wrote:
> Hi folks
>
> I'm trying to compile Sablotron 0.95 on HP-UX 11.11 with no luck.
>
> Doing the standard "INSTALL" routine.
>
>
> short 'make' output:
> ##
> c++ -DHAVE_CONFIG_H -I. -I. -I../../autocfg -g -O2 -c error.cpp -fPIC
-DPIC
> -o .libs/error.lo
> In file included from sablot.h:72,
> from sdom.h:39,
> from error.cpp:38:
> shandler.h:340: type specifier omitted for parameter `size_t'
> shandler.h:340: parse error before `*' token
> *** Error exit code 1
>
> Stop.
> ##
>
> gcc 3.1
>
> Let me know what more you need to know.
>
> With regards
> Frank Limstrand
> National Library of Norway
> ------------------------------------------------------------------------
>
> Archives and info: http://www.gingerall.org/charlie/ga/xml/m_ml.xml
>
> Mailing list maintained by Ginger Alliance


--
Pavel Hlavnicka
Ginger Alliance

呵呵

顺便汗一个,上面这个文章段若是从IE中复制过来,竟然没有换行的。。。,一点格式都没了哦。。。

还是火狐知我心意啊~~~

2007年9月5日星期三

c++位运算(收藏用)

c++位运算(收藏用)

楼主snowingbf(snowingbf)2004-10-19 00:03:07 在 C/C++ / C++ 语言 提问

前言
看到有些人对位运算还存在问题,于是决定写这篇文章作个简要说明。

什么是位(bit)?

很简单,位(bit)就是单个的0或1,位是我们在计算机上所作一切的基础。计算机上的所有数据都是用位来存储的。一个字节(BYTE)由八个位组成,一个字(WORD)是二个字节或十六位,一个双字(DWORD)是二个字(WORDS)或三十二位。如下所示:

0 1 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 0 1 0 0 0 1 1 1 1 0 0 0
| | | | | | |
| +- bit 31 | | | bit 0 -+ |
| | | | |
+-- BYTE 3 ---- -+---- BYTE 2 ---+---- BYTE 1 ---+--- BYTE 0 -----+
| | |
+------------ WORD 1 ------------+----------- WORD 0 -------------+
| |
+----------------------------- DWORD -----------------------------+

使用位运算的好处是可以将BYTE, WORD 或 DWORD 作为小数组或结构使用。通过位运算可以检查位的值或赋值,也可以对整组的位进行运算。

16进制数及其与位的关系
用0或1表示的数值就是二进制数,很难理解。因此用到16进制数。

16进制数用4个位表示0 - 15的值,4个位组成一个16进制数。也把4位成为半字节(nibble)。一个BYTE有二个nibble,因此可以用二个16进制数表示一个BYTE。如下所示:

NIBBLE HEX VALUE
====== =========
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 A
1011 B
1100 C
1101 D
1110 E
1111 F

如果用一个字节存放字母"r"(ASCII码114),结果是:
0111 0010 二进制
7 2 16进制

可以表达为:'0x72'

有6种位运算:
& 与运算
| 或运算
^ 异或运算
~ 非运算(求补)
>> 右移运算
<< 左移运算

与运算(&)
双目运算。二个位都置位(等于1)时,结果等于1,其它的结果都等于0。
1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0

与运算的一个用途是检查指定位是否置位(等于1)。例如一个BYTE里有标识位,要检查第4位是否置位,代码如下:

BYTE b = 50;
if ( b & 0x10 )
cout << "Bit four is set" << endl;
else
cout << "Bit four is clear" << endl;

上述代码可表示为:

00110010 - b
& 00010000 - & 0x10
----------------------------
00010000 - result

可以看到第4位是置位了。

或运算( | )
双目运算。二个位只要有一个位置位,结果就等于1。二个位都为0时,结果为0。
1 | 1 == 1
1 | 0 == 1
0 | 1 == 1
0 | 0 == 0

与运算也可以用来检查置位。例如要检查某个值的第3位是否置位:

BYTE b = 50;
BYTE c = b | 0x04;
cout << "c = " << c << endl;

可表达为:

00110010 - b
| 00000100 - | 0x04
----------
00110110 - result

异或运算(^)
双目运算。二个位不相等时,结果为1,否则为0。

1 ^ 1 == 0
1 ^ 0 == 1
0 ^ 1 == 1
0 ^ 0 == 0

异或运算可用于位值翻转。例如将第3位与第4位的值翻转:

BYTE b = 50;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;

可表达为:

00110010 - b
^ 00011000 - ^0x18
----------
00101010 - result

00101010 - b
^ 00011000 - ^0x18
----------
00110010 - result

非运算(~)
单目运算。位值取反,置0为1,或置1为0。非运算的用途是将指定位清0,其余位置1。非运算与数值大小无关。例如将第1位和第2位清0,其余位置1:

BYTE b = ~0x03;
cout << "b = " << b << endl;
WORD w = ~0x03;
cout << "w = " << w << endl;

可表达为:

00000011 - 0x03
11111100 - ~0x03 b

0000000000000011 - 0x03
1111111111111100 - ~0x03 w

非运算和与运算结合,可以确保将指定为清0。如将第4位清0:

BYTE b = 50;
cout << "b = " << b << endl;
BYTE c = b & ~0x10;
cout << "c = " << c << endl;

可表达为:

00110010 - b
& 11101111 - ~0x10
----------
00100010 - result

移位运算(>> 与 <<)
将位值向一个方向移动指定的位数。右移 >> 算子从高位向低位移动,左移 << 算子从低位向高位移动。往往用位移来对齐位的排列(如MAKEWPARAM, HIWORD, LOWORD 宏的功能)。

BYTE b = 12;
cout << "b = " << b << endl;
BYTE c = b << 2;
cout << "c = " << c << endl;
c = b >> 2;
cout << "c = " << c << endl;

可表达为:
00001100 - b
00110000 - b << 2
00000011 - b >> 2

译注:以上示例都对,但举例用法未必恰当。请阅文末链接的文章,解释得较为清楚。

位域(Bit Field)
位操作中的一件有意义的事是位域。利用位域可以用BYTE, WORD或DWORD来创建最小化的数据结构。例如要保存日期数据,并尽可能减少内存占用,就可以声明这样的结构:

struct date_struct {
BYTE day : 5, // 1 to 31
month : 4, // 1 to 12
year : 14; // 0 to 9999
}date;

在结构中,日期数据占用最低5位,月份占用4位,年占用14位。这样整个日期数据只需占用23位,即3个字节。忽略第24位。如果用整数来表达各个域,整个结构要占用12个字节。

| 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 |
| | | |
+------------- year --------------+ month+-- day --+

现在分别看看在这个结构声明中发生了什么

首先看一下位域结构使用的数据类型。这里用的是BYTE。1个BYTE有8个位,编译器将分配1个BYTE的内存。如果结构内的数据超过8位,编译器就再 分配1个BYTE,直到满足数据要求。如果用WORD或DWORD作结构的数据类型,编译器就分配一个完整的32位内存给结构。

其次看一下域声明。变量(day, month, year)名跟随一个冒号,冒号后是变量占用的位数。位域之间用逗号分隔,用分号结束。

使用了位域结构,就可以方便地象处理普通结构数据那样处理成员数据。尽管我们无法得到位域的地址,却可以使用结构地址。例如:
date.day = 12;
dateptr = &date;
dateptr->year = 1852;

2007年9月4日星期二

How do I get the date a file was last modified?

How do I get the date a file was last modified?

I wanted to be able to use the date that a file was last modified, to set the filename of another file. It took me quite a while to find out how to get hold of the date and it turns out that there are three dates on UNIX/Linux. You can use the stat() function.

 

#include

#include

#include



struct tm* clock; // create a time structure

struct stat attrib; // create a file attribute structure

stat("afile.txt", &attrib); // get the attributes of afile.txt

clock = gmtime(&(attrib.st_mtime)); // Get the last modified time and put it into the time structure



// clock->tm_year returns the year (since 1900)

// clock->tm_mon returns the month (January = 0)

// clock->tm_mday returns the day of the month

How do I use itoa() with GCC?

How do I use itoa() with GCC?

Arrgghh C/C++! It would appear that itoa() isn't ANSI C standard and doesn't work with GCC on Linux (at least the version I'm using). Things like this are frustrating especially if you want your code to work on different platforms (Windows/Linux/Solaris/whatever).

Many people say that you should just use sprintf to write to a character string but that doesn't allow for one of the features of itoa(); the ability to write in a base other than 10. This page contains a series of evolving versions of an itoa implementation. The oldest are near the top and the latest at the bottom. Please make sure that you use the latest version.

Credits

Before we go any further, I would like to say thanks to the people that contributed to the solutions below. This function has been put together by contributions from Stuart Lowe (that's me), Robert Jan Schaper, Ray-Yuan Sheu, Rodrigo de Salvo Braz, Wes Garland and John Maloney.

Development

Below is an early version described by Robert Jan Schaper on Google groups:

char* version 0.1

 

char* itoa(int val, int base){

static char buf[32] = {0};

int i = 30;

for(; val && i ; --i, val /= base)

buf[i] = "0123456789abcdef"[val % base];

return &buf[i+1];

}

This doesn't quite look like the implementation I am used to which is more like itoa(int value, char* buffer, int radix). In the end I have made my own version which uses a std::string instead of a character string.

std::string version 0.1

 

void my_itoa(int value, std::string& buf, int base){

int i = 30;

buf = "";

for(; value && i ; --i, value /= base) buf = "0123456789abcdef"[value % base] + buf;

}

Update: (2005/02/11)
Ray-Yuan Sheu sent me an email with an improved version which does a bit more error checking for things like a base that is out of range and for negative integers.

Update: (2005/04/08)
Rodrigo de Salvo Braz spotted a bug that meant nothing was returned when the input was zero. It now returns "0". This bug has also been spotted by Luc Gallant.

std::string version 0.2

 

/**

* C++ version std::string style "itoa":

*/

std::string itoa(int value, unsigned int base) {

const char digitMap[] = "0123456789abcdef";

std::string buf;



// Guard:

if (base == 0 || base > 16) {

// Error: may add more trace/log output here

return buf;

}



// Take care of negative int:

std::string sign;

int _value = value;



// Check for case when input is zero:

if (_value == 0) return "0";



if (value < 0) {

_value = -value;

sign = "-";

}



// Translating number to string with base:

for (int i = 30; _value && i ; --i) {

buf = digitMap[ _value % base ] + buf;

_value /= base;

}

return sign.append(buf);

}

Update: (2005/05/07)
Wes Garland says that lltostr exists under Solaris and several other unices. It should return a char * of a long long in multiple number bases. There is also ulltostr for unsigned values.

Update: (2005/05/30)
John Maloney has pointed out various problems with the previous implementation. One of the major issues was the amount of heap allocation going on. He suggested that a lot of this be removed to speed up the algorithm. Below are two versions based on his excellent suggestions. The char* version is at least 10 times faster than the code above. The new std::string version is 3 times faster than before. Although the char* version is faster, you should check that you have allocated enough space to hold the output.

std::string version 0.3

 

/**

* C++ version std::string style "itoa":

*/

std::string itoa(int value, int base) {



enum { kMaxDigits = 35 };

std::string buf;

buf.reserve( kMaxDigits ); // Pre-allocate enough space.



// check that the base if valid

if (base <> 16) return buf;



int quotient = value;



// Translating number to string with base:

do {

buf += "0123456789abcdef"[ std::abs( quotient % base ) ];

quotient /= base;

} while ( quotient );



// Append the negative sign for base 10

if ( value < 0 && base == 10) buf += '-';



std::reverse( buf.begin(), buf.end() );

return buf;

}

char* version 0.2

 

/**

* C++ version char* style "itoa":

*/

char* itoa( int value, char* result, int base ) {

// check that the base if valid

if (base <> 16) { *result = 0; return result; }



char* out = result;

int quotient = value;



do {

*out = "0123456789abcdef"[ std::abs( quotient % base ) ];

++out;

quotient /= base;

} while ( quotient );



// Only apply negative sign for base 10

if ( value < 0 && base == 10) *out++ = '-';



std::reverse( result, out );

*out = 0;

return result;

}

Update: (2006/10/15)
Luiz Gonçalves tells me that although not an ANSI standard, itoa comes in many packages and it is written in many textbooks. He suggests a version written in pure ANSI C based on a version from Kernighan & Ritchie's Ansi C. A base error is reported by the return of an empty string but the function does no checks of sizes and no allocations. This version is provided below along with a slightly modified version (architecture specific tweaks), the std::string version and the C++ char* itoa() version.

 

/**

* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C":

*/

void strreverse(char* begin, char* end) {

char aux;

while(end>begin)

aux=*end, *end--=*begin, *begin++=aux;

}

void itoa(int value, char* str, int base) {

static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";

char* wstr=str;

int sign;



// Validate base

if (base<2>35){ *wstr='\0'; return; }



// Take care of sign

if ((sign=value) < 0) value = -value;



// Conversion. Number is reversed.

do *wstr++ = num[value%base]; while(value/=base);

if(sign<0) *wstr++='-';

*wstr='\0';



// Reverse string

strreverse(str,wstr-1);

}
 

/**

* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C"

* with slight modification to optimize for specific architecture:

*/

void strreverse(char* begin, char* end) {

char aux;

while(end>begin)

aux=*end, *end--=*begin, *begin++=aux;

}

void itoa(int value, char* str, int base) {

static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";

char* wstr=str;

int sign;

div_t res;



// Validate base

if (base<2>35){ *wstr='\0'; return; }



// Take care of sign

if ((sign=value) < 0) value = -value;



// Conversion. Number is reversed.

do {

res = div(value,base);

*wstr++ = num[res.rem];

}while(value=res.quot);

if(sign<0) *wstr++='-';

*wstr='\0';



// Reverse string

strreverse(str,wstr-1);

}

Latest Versions

Below are the latest versions of the itoa function using either char* or std::string as you prefer. I haven't included the Kernighan & Ritchie based versions in this section because I'm not sure what the copyright status is for those. However, the functions below have been developed by the people mentioned on this page and are available for use.

std::string version 0.3



/**

* C++ version std::string style "itoa":

*/

std::string itoa(int value, int base) {



enum { kMaxDigits = 35 };

std::string buf;

buf.reserve( kMaxDigits ); // Pre-allocate enough space.



// check that the base if valid

if (base <> 16) return buf;



int quotient = value;



// Translating number to string with base:

do {

buf += "0123456789abcdef"[ std::abs( quotient % base ) ];

quotient /= base;

} while ( quotient );



// Append the negative sign for base 10

if ( value < 0 && base == 10) buf += '-';



std::reverse( buf.begin(), buf.end() );

return buf;

}

char* version 0.2



/**

* C++ version char* style "itoa":

*/

char* itoa( int value, char* result, int base ) {

// check that the base if valid

if (base <> 16) { *result = 0; return result; }



char* out = result;

int quotient = value;



do {

*out = "0123456789abcdef"[ std::abs( quotient % base ) ];

++out;

quotient /= base;

} while ( quotient );



// Only apply negative sign for base 10

if ( value < 0 && base == 10) *out++ = '-';



std::reverse( result, out );

*out = 0;

return result;

}

Performance Comparsion

I've done some testing of the three versions of itoa by finding the average time required to perform 2 million conversions in every base from base 2 to base 20. The summary is presented in the following table.

functionspeed
C++ version 0.2 char* style "itoa"
char* itoa(int value, char* result, int base)
baseline
Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C"
void itoa(int value, char* str, int base)
~15% faster
Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" with modification to optimize for specific architecture
void itoa(int value, char* str, int base)
~30% faster
(XP, cygwin, g++)
C++ version 0.3 std::string style "itoa"
std::string itoa(int value, int base)
~3400% slower


If anyone has any improvements or better solutions, please let me know. My email address can be worked out from information on my home page.

2007年8月24日星期五

switc分支中initialization of 'j' is skipped by 'case' label错误

....
case 1:
for(int j=1;j<11;j++)
vc2->;push_back(j);
break;
case 2:
for(int j=0;j<11;j++)
vc2->;push_back(j*2);
break;
....
编译出错信息:
c:\microsoft visual studio\myprojects\jd2\jd.cpp(19) : error C2360: initialization of 'j' is skipped by 'case' label
为什么啊
请教请教

重复定义吧
你定义了j两次

请教请教

-->
没这回事吧?C++允许局部定义的,用来做循环控制的变量一般都是随用、随时定义。编译提示是,case可能跳过j的初始化。
估计是VC的问题。前几天我还写了一个含有类似语句的程序,gcc/g++编译没问题。

请教请教

:shock:

请教请教

VC 6 不能这样。
标准 C++ 是支持的。

请教请教

你是不是在后面又用到变量j了?
这种情况还是把j放到函数头里去定义吧。


muyilion
帅哥哟,离线,有人找我吗?


等级:新手上路
文章:13
积分:274
注册:2005年12月26日

下来了,可是编译时出现如下错误:
error C2360: initialization of 'i' is skipped by 'case' label
f:\xrx-exam\binarytree\main.cpp(830) : see declaration of 'i'
Error executing cl.exe.
求助!

2005-12-27 18:56:00
热情依然
帅哥哟,离线,有人找我吗?


来自:广州市
等级:版主
威望:22
文章:691
积分:15676
注册:2005年4月5日

你在每个case后加个大括号就可以了,这个是重复定义的问题,可能你的编译器不是vc.net如果是用vc6.0就会出现这个问题。有时间我会改改


c++/C + 汇编 = 天下无敌
2005-12-29 9:48:00
nqq622
帅哥哟,离线,有人找我吗?


等级:新手上路
文章:11
积分:136
注册:2006年1月4日
不错,我还得加油啊!

2007年8月14日星期二

使用printf输出各种格式的字符串

+--------------------------------------------+
| 主题: 使用printf输出各种格式的字符串 |
| |
| 日期: 2004-06-29 |
+--------------------------------------------+

1. 原样输出字符串:
printf("%s", str);

2. 输出指定长度的字符串, 超长时不截断, 不足时右对齐:
printf("%Ns", str); --N 为指定长度的10进制数值

3. 输出指定长度的字符串, 超长时不截断, 不足时左对齐:
printf("%-Ns", str); --N 为指定长度的10进制数值

4. 输出指定长度的字符串, 超长时截断, 不足时右对齐:
printf("%N.Ms", str); --N 为最终的字符串输出长度
--M 为从参数字符串中取出的子串长度

5. 输出指定长度的字符串, 超长时截断, 不足时左对齐是:
printf("%-N.Ms", str); --N 为最终的字符串输出长度
--M 为从参数字符串中取出的子串长度

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=488866

2007年8月10日星期五

C中的可变参数研究




一. 何谓可变参数


int printf(const char* format, ...);
这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用”…”表示). 而我们又可以用各种方式来调用printf,如:
printf("%d",value);
printf("%s",str);
printf("the number is %d ,string is:%s", value, str);


二. 实现原理
C语言用宏来处理这些可变参数。这些宏看起来很复杂,其实原理挺简单,就是根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参 数的地址。下面我们来分析这些宏。在VC中的stdarg.h头文件中,针对不同平台有不同的宏定义,我们选取X86平台下的宏定义:
typedef char *va_list;
/*把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的*/
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
/*_INTSIZEOF(n)宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟sizeof(int)对齐。一般的sizeof (int)=4,也就是参数在内存中的地址都为4的倍数。比如,如果sizeof(n)在1-4之间,那么_INTSIZEOF(n)=4;如果 sizeof(n)在5-8之间,那么_INTSIZEOF(n)=8。*/
#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) )
/*va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start (ap, v)以后,ap指向第一个可变参数在的内存地址*/
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
/*这个宏做了两个事情,
①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。*/
  #define va_end(ap) ( ap = (va_list)0 )
/*x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这 样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. */

以下再用图来表示:

在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|——————————————————————————|
|最后一个可变参数 | ->高内存地址处
|——————————————————————————|
...................
|——————————————————————————|
|第N个可变参数 | ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|——————————————— |
………………………….
|——————————————————————————|
|第一个可变参数 | ->va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|——————————————— |
|———————————————————————— ——|
| |
|最后一个固定参数 | -> start的起始地址
|—————————————— —| .................
|—————————————————————————— |
| |
|——————————————— |-> 低内存地址处

三. printf 研究

下面是一个简单的printf函数的实现,参考了中的156页的例子,读者可以结合书上的代码与本文参照。
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...) //一个简单的类似于printf的实现,//参数必须都是int 类型
{
char* pArg=NULL; //等价于原来的va_list
char c;

pArg = (char*) &fmt; //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值
pArg += sizeof(fmt); //等价于原来的va_start

do
{
c =*fmt;
if (c != '%')
{
putchar(c); //照原样输出字符
}
else
{
//按格式字符输出数据
switch(*++fmt)
{
case 'd':
printf("%d",*((int*)pArg));
break;
case 'x':
printf("%#x",*((int*)pArg));
break;
default:
break;
}
pArg += sizeof(int); //等价于原来的va_arg
}
++fmt;
}while (*fmt != '\0');
pArg = NULL; //等价于va_end
return;
}
int main(int argc, char* argv[])
{
int i = 1234;
int j = 5678;

myprintf("the first test:i=%d",i,j);
myprintf("the secend test:i=%d; %x;j=%d;",i,0xabcd,j);
system("pause");
return 0;
}
在intel+win2k+vc6的机器执行结果如下:
the first test:i=1234
the secend test:i=1234; 0xabcd;j=5678;

四. 应用
求最大值:
#include //不定数目参数需要的宏
int max(int n,int num,...)
{
va_list x;//说明变量x
va_start(x,num);//x被初始化为指向num后的第一个参数
int m=num;
for(int i=1;i {
//将变量x所指向的int类型的值赋给y,同时使x指向下一个参数
int y=va_arg(x,int);
if(y>m)m=y;
}
va_end(x);//清除变量x
return m;
}
main()
{
printf("%d,%d",max(3,5,56),max(6,0,4,32,45,533));
}

C的宏定义问题,如何让宏定义支持可变参数形式?

在RedHat中用CC编译器可以定义如下宏:

#define RunLog(fmt,arg1...) { debugLog(__FILE__,__LINE__,fmt,##arg1); }

应用时,可以RunLog("%s,%d,%x", strvar, intvar, pvar);
只要把 RunLog定义为 空 就可以去掉这些调试信息。调试程序时非常方便。

但在ScoUnix下的CC就不行了。。。
俺只好做了如下定义:

#define DebugPRT(str) { debugLog(__FILE__,__LINE__,str); }
#define DebugPRT1(fmt,arg1) { debugLog(__FILE__,__LINE__,fmt,arg1);}
#define DebugPRT2(fmt,arg1,arg2) { debugLog(__FILE__,__LINE__,fmt,arg1,arg2);}
#define DebugPRT3(fmt,arg1,arg2,arg3) { debugLog(__FILE__,__LINE__,fmt,arg1,arg2,arg3);}
#define DebugPRT4(fmt,arg1,arg2,arg3,arg4) { debugLog(__FILE__,__LINE__,fmt,arg1,arg2,arg3,arg4);}

别笑我笨。:oops:

各位高人指点一下。Sco下的CC该如何定义支持可变参数的宏????

我在SCO 5。0。x上面做这样的程序是可以的啊

用可变参数宏(variadic macros)传递可变参数表


作者: ZDNet China
2003-08-08 14:36:43


本文译自Builder.com,未经许可请勿转载你可能很熟悉在函数中使用可变参数表,如:

void printf(const char* format, …);

直到最近,可变参数表还是只能应用在真正的函数中,不能使用在宏中。

C99编译器标准终于改变了这种局面,它允许你可以定义可变参数宏(variadic macros),这样你就可以使用拥有可以变化的参数表的宏。可变参数宏就像下面这个样子:

#define debug(…) printf(__VA_ARGS__)

缺省号代表一个可以变化的参数表。使用保留名 __VA_ARGS__ 把参数传递给宏。当宏的调用展开时,实际的参数就传递给 printf()了。例如:

Debug(“Y = %d ”, y);

而处理器会把宏的调用替换成:

printf(“Y = %d ”, y);

因为debug()是一个可变参数宏,你能在每一次调用中传递不同数目的参数:

debug(“test”); //一个参数

可变参数宏不被ANSI/ISO C++ 所正式支持。因此,你应当检查你的编译器,看它是否支持这项技术。



Visual C++ 编码规范

1.说明

执行本规范的目的在于保证编码的规范性、和确保代码质量,提高软件代码的可读性、可修改性,避免在开发和调试过程中出现不必要的麻烦。此外,还可以统一代码风格,便于研发人员之间的技术交流。

2.适用范围

本标准适用于使用Visual C++进行软件程序开发的人员。

3.编码的基本要求

n 程序结构清晰,逐步求精,单个函数的程序行数一般不超过100行。

n 代码精简,避免编码拖沓冗长。

n 尽量使用标准库函数和公共函数。

n 不要随意定义全局变量,尽量使用局部变量、类的成员变量。

n 表达式复杂时,多使用括号以避免二义性。

3.1可读性

n 可读性第一,效率第二。

n 保持注释与代码完全一致。

n 每个源程序文件,都有文件头说明,说明规格见规范。

n 每个函数,都有函数头说明,说明规格见规范。

n 主要变量(结构、联合、类或对象)定义或引用时,注释能反映其含义。

n 常量定义(DEFINE)有相应说明。

n 处理过程的每个阶段都有相关注释说明。

n 在典型算法前都有注释。

n 利用缩进来显示程序的逻辑结构,缩进量一致并以空格键为单位,定义为 4个
字节,不建议用Tab键。

n 循环、分支层次不要超过五层。必要时,使用Goto 语句,减少层次。

n 注释可以与语句在同一行,也可以在上行。

n 空行和空白字符也是一种特殊注释。

n 一目了然的语句不加注释。

n 注释的作用范围可以为:定义、引用、条件分支以及一段代码。

n 注释行数(不包括程序头和函数头说明部份)应占总行数的 1/5 到 1/3 。

3.2结构化要求

n 代码严格缩进,缩进使用TAB键,设置TAB键等价于4个空格字符。

n 禁止出现两条等价的支路。

n 减少使用GOTO语句。为减少IF语句层次,也可以适当使用。

n CASE 实现多路分支。减少使用 ELSE IF句型。

n 避免从循环引出多个出口。

n 函数一般只有一个出口。为减少判断语句层次,可适当使用GOTO 语句。

n 不使用条件赋值语句。 如:if (a=b) …

n 不要轻易用条件分支去替换逻辑表达式。

n 考虑到代码量和可阅读性,有如下编码建议(非强制性规范):

1)减少括号的使用

if (!m_bIsOpen ) return FALSE;

如果写成,将大大增代码量,函数体代码语句多的时候不利于阅读:

if (!m_bIsOpen )

{

return FALSE;

}

else

{

// ......

}

2)使用GOTO语句减少语句层次

下面是函数体,使用GOTO语句减少代码层次的例子

该例子如使用IF语句做整体判断则明显增加代码缩进的层次。

{

if ( 条件不满足1 )

{

iret = -1;

goto label_end;

}

if ( 条件不满足2 )

{

iret = -2;

goto label_end;

}

label_end:

// ... ...

return iret;

}

3.3正确性与容错性要求

n 程序首先是正确,其次是才是优美。

n 无法证明你的程序没有错误,因此在编写完一段程序后,应先回头检查。

n 改一个错误时可能产生新的错误,因此在修改前首先考虑对其它程序的影响。

n 所有变量在调用前必须被初始化。数组、结构体要ZeroMemory 初始化。

n 对所有的用户输入,必须进行合法性检查。

n 不要比较浮点数的相等,如: 10.0 * 0.1 == 1.0 , 不可靠

n 程序与环境或状态发生关系时,必须主动去处理发生的意外事件,如文件能否逻辑锁定、打印机是否联机等。

n 单元测试也是编程的一部份,提交联调测试的程序必须先由程序员经过单元测试。

3.4可重用性要求

n 重复使用的、相对独立功能的算法或代码应抽象为公共控件或类。

n 公共控件或类应考虑使用OO编程思想,减少外界联系,考虑独立性和封装性。

n 公共控件或类应建立使用模板。

n 一些涉及到商业机密或者公司核心技术的部件,可使用DLL或者COM组件的形式对公用模块进行封装。

4. C++代码书写规范

4.1文件头的编写规范

每个文件开头部分至少应当包括下面几项内容:文件标题(Title )、版权说明(Copyright)、修改记录(Modification History)。

n Title 中包括文件的名称和文件的简单说明。Title 应该在一行内完成。

n Copyright 包括一个版权说明。

例如:

// Copyright (c) 2005 EASTCOM LingTong.

// All rights reserved.

n Modification History

记录文件修改的过程,只记录最主要的修改。当书写新的函数模块时,只需要使用

形如“add func1()”这样的说明;如果后面又对函数中的算法进行了修改,需要指出修改

的具体位置和修改方法。

Modification History 的具体格式为:

<版本号> <修改日期> < 修改人和修改动作>

如:

// V0.9.0 20050823 创建初始版本

// V0.9.1 20050830 修改结构体项目对齐方式为默认

一个文件头的具体范例如下:

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

//

// Copyright (c) 2005 EASTCOM LingTong.

// All rights reserved.

//

// 文件名 CCFrmw.h

// 类型 HPP

// 版本号 0.9.1

// 编制 Trueway Lee

// 描述 用于实现 ABWOA 模块DLL

// 该模块内部实现了 EASTCOM-ABWOA 软件总线机制

//

//--------------------------------------------------------------------

// 版本历史

//--------------------------------------------------------------------

// V0.9.0 20050823 创建初始版本

// V0.9.1 20050830 修改结构体项目对齐方式为默认

//

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

4.2变量命名

为了便于程序的阅读,变量命名必须具有一定的实际意义。

变量命名按照匈牙利命令风格:形式为xAbcFghx由变量类型确定,AbcFgh表示连续意义字符串,如果连续意义字符串仅两个,可都大写。如OK。

下面是常规数据类型的变量命名方法:

类型名

C/C++ 等价类型

定义实例

BOOL

int

BOOL fFlag

LPSTR

char *

LPSTR szText

HANDLE

void *

HANDLE hEvent1

HWND

void *

HWND hWnd

INT

int

INT iIndex

UINT

unsigned int

UINT nIndex

LPVOID

void *

LPVOID pData

WORD

unsigned short

WORD wData

DWORD

unsigned long

DWORD dwData

BYTE

unsigned char

BYTE bChar

CHAR

char

CHAR cChar

LONG

long

LONG lIndex

ULONG

unsigned long

ULONG ulData

WPARAM

unsigned int

WPARAM wParam

LPARAM

long

LPARAM lParam

这里只是列举了一部分,更详细的内容请查看 MSDN 文档。

4.3各作用域变量命名规则

n 全局变量(g_类型、)

“g_” + “类型标识”+“变量名”

例如:g_nMsg、g_bFlag

n 类成员变量(m_xxxx)

“m_” + “类型标识”+“变量名”

例如:m_szText、m_hEvent

n 局部变量

“类型标识”+“变量名”

局部变量中可采用如下几个通用变量:nTemp,iRet,I,J(一般用于循环变量)。

n 函数参数变量,参数变量是局部变量的特例

a_” +“类型标识”+“变量名”

例如:int test_fun(long a_lMode, LPSTR a_szData);

4.4常量命名和宏定义

下面是常量和宏定义方面的一般规则:

n 常量和宏定义必须具有一定的实际意义;

n 常量和宏定义在 #include 和函数定义之间;

n 常量和宏定义一般全部以大写字母来撰写,中间可根据意义的连续性用下划线连接,每一条定义的右侧必须有一简单的注释,说明其作用。

#define _POSIX_ARG_MAX 4096

#define _POSIX_CHILD_MAX 6

#define _POSIX_LINK_MAX 8

#define _POSIX_MAX_CANON 255

#define _POSIX_MAX_INPUT 255

#define _POSIX_NAME_MAX 14

考虑到阅读的方便性,也可以使用大小写混合的形式定义常量,但第一段必须是大写,标识该常量的使用访问或者类别。

#define ERROR_SSIPC_Name_Error (-1)

#define ERROR_SSIPC_Time_Out (-2)

#define ERROR_SSIPC_Call_Failture (-3)

#define ERROR_SSIPC_Pack_Data (-4)

n 资源名字定义格式:
菜单:IDM_XX或者CM_XX
位图:IDB_XX
对话框:IDD_XX
字符串:IDS_XX
DLGINIT:DIALOG_XX
ICON:IDR_XX

4.5函数命名

n 函数原型说明包括引用外来函数及内部函数,外部引用须在右侧注明函数来源:模
块名及文件名, 如是内部函数,不需要特殊说明。

n 函数名第一个字母大写,其他部分要求用大小写字母组合命名。

必要时可用下划线间隔。

示例如下:

extern void UpdateDB_Tfgd (TRACK_NAME) //Module Name r01/sdw.c
extern PrintTrackData (TRACK_NAME)
//Module Name
r04/tern.c
extern ImportantPoint (void)
//Module Name
r01/sdw.c
void ShowChar (int
int long)

void ScrollUp_V (int
int)

4.6结构体

n 结构体类型命名使用大写字母,原则上前面以下划线开始;

n 结构体变量用大小写字母组合,第一个字母大写,必要时可用下划线间隔;

n 对于私有数据区,须注明其所属的进程。全局数据定义只需注明其用途。


示例如下:
typedef struct
{
char szProductName[20]

char szAuthor[20]

char szReleaseDate[16]

char szVersion[10]

unsigned long MaxTables

unsigned long UsedTables

}DBS_DATABASE, * LPDBS_DATABASE

DBS_DATABASE g_dataBase

4.7控件的命名

用小写前缀表示类别
fm 窗口
cmd 按钮
cob combo,下拉式列表框
txt 文本输入框
lab label,标签
img image,图象
pic picture
grd Grid,网格
scr 滚动条
lst 列表框
frm fram

控件命名可以为:

n 小写类别+大小写混合的控件标识

n 小写类别+“__”+ 控件标识

比如: labText、lab_text 都可以。

4.8注释

n 原则上注释要求使用中文;

n 文件开始注释内容包括:公司名称、版权、作者名称、时间、模块用途、背景介绍等;

n 复杂的算法需要加上流程说明;

n 函数注释包括:输入、输出、函数描述、流程处理、全局变量、调用样例等;

n 复杂的函数需要加上变量用途说明;

n 程序中注释(阶段注释)包括:修改时间和作者、方便理解的注释等;

注释主要分为三类,文件头注释、函数体内阶段注释、函数注释

引用一: 文件开头的注释模板

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

//

// Copyright (c) 2005 EASTCOM LingTong.

// All rights reserved.

//

// 文件名: cccdmfw_xpp.h

// 类型: XPP

// 版本号: 0.1

// 编制: jjt

// 描述: 用于实现 ABWOA 出钞机构模块定义

// 该模块内部实现了 EASTCOM-ABWOA 模块出钞机构的动作,

// 基于ABWOA模块的出钞机构CCCdmFW类定义

//

//--------------------------------------------------------------------

// 版本历史:

//--------------------------------------------------------------------

// V0.1 2005-9-22 10:30 创建初始版本

//

//

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


引用二: 程序中的注释模板

if ( ( pBuff->sCallType == SWBUS_Asynchronization_Mode ) &&

( pBuff->pJobState ) )

pBuff->pJobState->sJobState = CCFRMW_JOB_RUNNING;// 设置任务状态

//---------------------------------------------------------------------

// 获得模块信息

//---------------------------------------------------------------------

LPMYMODULENODE lpInfo = NULL;

int iret = g_load.GetModuleInfo(pBuff->szFWName, &lpInfo);

if ( iret != CCFRMW_RC_OK ) return iret;

//---------------------------------------------------------------------

// 获得模块相关的对象信息(找到最后的子类,作为服务对象)

//---------------------------------------------------------------------

LPMYMODULEDETAIL lpDetail = NULL;

iret = g_load.GetFirstObjectInfo(lpInfo, &lpDetail);

if ( iret != CCFRMW_RC_OK ) return iret;


引用三: 函数开头的注释模板,考虑到编码的方便性,函数注释可以简单一点

// 获得DD变量值

SHORT CCDataDicFW::GetDataItem( // get the data item - USHORT

CHAR * pszDataItemName, // (in) property name

USHORT & rusVar, // (out) buffer for retrieved data

ULONG ulIndex) // (in) index (used for indexing arrays)

{

SHORT sRc = CCDATADICTFW_OK;

char ownerFW[NODE_VALUE_LEN];

SHORT funcId;

TrcWritef(TRC_ENTRY,"> CCDataDicFW::GetDataItem(%s)",pszDataItemName);

sRc = GetDataInfo(GET,ownerFW,funcId,pszDataItemName);

if(sRc == CCDATADICTFW_OK)

{

sRc = FrmResolve(ownerFW,funcId, pszDataItemName, strlen(pszDataItemName)+1, (void *)&rusVar, sizeof(USHORT), NULL, 0, ulIndex);

}

TrcWritef(TRC_ENTRY,"<>

return sRc;

}

4.9 限制使用全局变量

考虑到代码移植方面和出现信息存贮冲突,在代码中,避免使用全局变量。

可以进行替代:

n 使用类的成员变量

n 使用类的静态变量

n 线程入口函数使用参数传递指针

n 一些不能避免的情况,如:WINPROC 中的变量访问,仍可以使用全局变量。

4.10 数据初始化

与很多语言不同,C/C++一般不提供对结构体变量、字符数组、整形等数据的初始化控制。比如 integer I; 此语句执行后,I 里面的值是一个随机数字。

char buff[100]; 执行后,buff 的每个单元的内容也是一个随机值。

为了避免出现问题,要求变量申明后,一定要初始化,这里还包括了类的成员变量。

比如:

char buff[100];

ZeroMemory(buff, sizeof(buff));

下面针对各类数据类型做数据初始化的规范说明:

n 简单类型数据,需要在变量标识后加“=初始值;”

integer i = 0;

char c = 0;

double d = 12.0;

n 指针变量、类似指针的变量,在申请内存前和使用完成后,都要使值保持NULL

HANDLE p = NULL;

// ......

if (p )

{

CloseHandle(p);

p = NULL;

}

n 字符数组,注意{0} 的妙用

char buff[100];

ZeroMemory(buff, sizeof(buff));

也可以写成一句话:

char buff[100] ={0};

n 结构体变量,注意{0} 的妙用

MYSTRUCTA bb;

ZeroMemory(&bb, sizeof(MYSTRUCTA ));

也可以写成一句话:

MYSTRUCTA bb = {0};

n C++类中,在类的构造子中完成对成员变量数据的初始化

5.调试规范

5.1 Trace跟踪

n 程序中应该包括足够的调试信息。调试信息一律使用Trace跟踪。

n 在函数入口处,推荐使用assert() 对于参数的有效性进行判断。

n 对于入口参数是指针的,必须使用Assert()进行判断。

n assert()的使用方法为:assert(<逻辑表达式>)assert()的具体使用方法请参考MSDN文档,这里不做详细说明。

n 在代码中易出现问题的地方,也应该使用assert()、或者其他的机制进行相应的检测。

5.2调试记录、测试报告

调试记录是软件产品的一个重要组成部分。在软件的调试过程中,应做好调试记录,以便于软件的后续开发和维护工作。

调试的文档包括两个内容:

n 调试记录

即发现软件中的Bug 并进行修改的记录。

在软件编码过程中的修改很频繁,对每个发现的问题都记录下来是不现实的。在实际记录过程中可选择以下信息的部分作为记录内容,调试记录一般需要包括如下内容:

(日期)

[如:2000 6 26 ]

(问题编号)

[如:1]

(问题征兆)

[如:发现缓冲中的报文被无端修改]

(问题根源及解决方法)

[如:由于在多任务环境中对共享资源没有使用互斥机制,对于缓冲增加一个信号量进行保护(修改位于buffer.c 中)]

n 测试报告

在软件项目实现阶段,除了要编写好代码以外,经常需要对实现的子模块进行测试,尤其是对提供给他人使用的模块,必须进行测试。

在测试前,需要做测试计划,测试中做好零散的记录,测试完成后,整理成报告。测试报告是一笔宝贵的财富,当软件做到一定程度的功能,以后对软件的更新,可以参照历史测试报告进行回归测试。减少重新编制测试报告的工作量,同时能保存测试指标,使软件再测试具有延续性。

5.3测试用例、测试代码

为使测试自动化和更具全面性,需花费时间和精力制作测试用例和编写测试代码,比如测试类的功能,就需要编写测试代码。

测试用例需要按照软件工程学的相关规范来制作。最简单的原则是,测试用例要能覆盖问题可能出现的范围。详细测试用例的制作方法参考软件工程学资料。

测试代码需要实现对单个模块的模拟调用,在整个项目相关的软件模块并为一一齐备的情况下,也需要模拟实现未真正实现的功能模块。此外,测试代码还可以用于构建软件模块的压力测试、内存泄漏测试。

5.4调试、测试要求

n 对于测试代码和测试用例,要注意保存,并适当使用文档加以说明。

文档中应该说明测试代码的测试目的。如果测试代码和文档分别存放,还需要在文档中对测试代码的保存位置加以说明。

n 写好测试报告。例如:对某个模块的若干接口加以测试,要记录测试的目的、具体的测试方法和测试的结果(如:通过测试,或测试失败)。

n 出现问题,及时通知相关模块具体负责人。

测试人员在测出问题后,除完成测试报告外,还需要和模块负责人进行必要的沟通。

6.源码管理办法

1. 修改及时提交

源码完成编制、修改后,须提交到配置库。使用指定的版本管理软件实施控制。

当某功能模块完成编程后,使用软件管理工具提交。在进行提交之前,必须对新加入的功能进行一定的测试,在尽量保证正确性后在进行提交。

避免出现长时间修改而不提交的情况,因为可能出现多人修改同一模块的情况。长时间不提交将导致并发的修改方每次修改前不能获得最新的版本。

2. 提交要完整

每次提交,要注意完成性,把本次涉及到的修改都做提交,否则容易出现别处下载更新代码后不能编译、调试的情况。

3. 先更新,再修改

每次进行修改前,从配置库“Check out”最新版本到本地,以免在老版本上做修改。

4. 写修改说明

每次提交,须在提交报告中注明新加入功能的内容、修改的文件范围等内容。

5. 定期更新本地版本

需要定期对本地版本的代码、文档做全面更新,特别是在软件项目庞大或者项目组人员较多的情况下,需要定期更新本地版本,特别是非自身负责的模块,以便能在最新版本的代码上进行联合编译、调试和测试。

6. 共用的.H、.CPP、.LIB、.DLL文件单独存放

类似VC++的安装目录安排,需设置INCLUDE、LIB、SRC等共有目录,将共有代码放置其内,避免出现每个模块将共有代码放在各自的项目内的情况。

单独存放共用代码,利于统一更新。

7. 配置库中除管理源代码外,还管理 BIN 内容

阶段编译结果的EXE、DLL、OCX等内容,也作为配置项,减少不必要的重新编译。

8. 阶段成果分开保存

针对相对稳定的阶段成果,需和主配置分开保存。以避免最新的修改影响阶段成果。