Archive for December, 2006
行业标准&潜规则–How to write solid and efficient code?(3)
Posted by xiaokucha in Programming on December 21, 2006
{
char *__old=(s);
int __len = strlen(s);
char *__new=(char *)malloc(__len+1);
__new[__len]=”;
memcpy(__new,__old,__len);
return __new;
好吧,把上面的问题程序重新写一下:
What is your perfect major?…
so,我还是个数学天才. You scored as Mathematics. You should be a Math major! Like Pythagoras, you are analytical, rational, and when are always ready to tackle the problem head-on!
What is your Perfect Major? (PLEASE RATE ME!!<3) 行业标准&潜规则– How to write solid and efficient code?(2)Posted by xiaokucha in Programming on December 14, 2006 Above All
寡人声明,这些内容都是寡人从网上收集的,目的就是做个3c(collection&compilation&combination),别tmd的说我copy。
First,逐个解决(1)中的问题
题目一:
1、选择f的理由
因为非NULL是约定,所以可以确定是调用者的问题,f可以明确地指出这一 点,防止错误扩散。防止错误扩散的意思是,如果用其他方式,比如throw exception的方式,这个异常不一定会在调用此函数的上一层被捕捉到,可 能会被继续抛出直到最上一层或者直到在某一层被catch到,这样的话,错误 就会距离发生地点很远,扩散开来。 2、反对用f 3、另外一种观点 4、一种反对d的理由 5、关于观点3的支持意见 6、选择d+f (a) if (!pParam) (b) if (!pParam) 编写优质无错代码的经验: 理想的编译器和实际的编译器:
a.把屡次出错的合法的C习惯用法看成程序中的错误 b.增强编译器的警告级别 c.使用其它的工具来检查代码 如 Lint 等 d.进行单元测试 e.消除程序错误的最好方法是尽可能早、尽可能容易地发现错误,要寻求费力最小的自动查错的方法 f.努力减少程序员查错所需的技巧 题目二:
a.该函数检查ch是否在A..Z之间,如果是,则返回相应的小写字符,如果
不是,则返回-1。 缺点在于:把错误标志值和计算结果混在一起使用,容易造成使用者的误会。 b.该函数使用了断言,如果ch在A..Z之间则返回相应的小写字符,如果不是, 断言会起作用,程序发生错误并退出。而最后一个return ch;则是在 release的时候,如果不是A..Z之间,则返回原来的字符。但是,从书写效 率上来说,这个函数稍微罗嗦了一点。因为它重复使用了断言和if判断。 c.该函数也使用了断言,返回相应大写字母的小写字母。 使用断言的好处: 断言使用举例: 使用断言的规则: 断言小结: 题目三:
首先对memset()扫一下盲:
/*
Sets the first "count" bytes of the memory starting at "dst" to the
character value "val".
For example:
int *p;
p=(int *)malloc(0x400*sizeof(int));
memset(p,0,0×400);
*/
void* memset(void *dst, int val,int count)
{
byte* p=(byte*)dst;//void *p=dst;
while(count–){
*p++=b;//*(char *)dst=(char)val;dst=(char *)dst+1;
}
return dst;
}
int
main()
{
char str[]="fuck9c";//这里
memset(str,’x’,strlen(str));
puts(str);
system("pause");
return 0;
}
这里还要说一些题外的东西,上面的char str[]为什么不能是char *str呢?
可以尝试一下用dev-c++ 4.9.9.2编译是通过的,但是运行时出现了错误报告,
用gdb r了一下发现是segment fault,why?
如下:
char *p="hello"不应该存在于今天的C++程序中了。
这种写法完全是为了保持对C中过去通行的(错误的) 写法的兼容性而对C++类型系统不得已的破坏。 不仅从原理上毫无道理,正如RoachCock所言,由p改写
"hello"会直接引发CPU异常。 此写法已被声明为deprecated,这意味着在未来的某一天
你的程序将不能通过编译。 in iso 2.13.4 string literal
item 1: … An ordinary string literal has type "array of n const char" and static storage duration… item 2: … The effect of attempting to modify a string literal is undefined. 4.2 Array-to-pointer conversion
item 2: A string literal … can be converted to an rvalue of type "pointer to char": … [Note: this conversion if deprecated. … 如果你新买的C/C++教材还在用这样的写法,应该立即把它扔掉。
可以这样理解:这句话 char* p="abc"; 里的"abc"并非常量,而是以常量区的"abc"为源,在栈区里新申请的一个空间 虽然和书上的理论不符,但编译器是怎么做的就难说了
因为指向常量的指针不能够自动转换成不是指向常量的指针,反之则可以
—————————————————————– 我也觉得这个原因, 觉得VS2005的结果没有错. 编译器对语法的具体实现仁者见仁了。 GCC里输出为 foo( const char* ) catch( const char* ) 指出一点:C++标准规定,字面字符串常量,像"abc",属于const char *。这一点是没有疑问的。但是现存的char *p = "hello,world",这样的代码太多了,如果严格按照标准来这种初始化是不能成立的,所以C++标准网开一面(还是为了向下兼容),特别允许这种语句合法。或者说,法外施恩来保证那些像楼主所说的char *到const char *的自动转换能够进行。但这不表明"abc"就是char *了,如果char *p = "abc",若试图修改p[0]就会引发一个段错误。关键在于"abc"存放于全局数据段。可以拿下面一个例子看:
#include <cstdio>
void f()
{ char *p = "abc"; std::printf("%pn",p); } int main()
{ char *p = "abc"; std::printf("%pn",p); return 0; } 运行一下看看,两个p指向的是同一个地址。之所以编译器能这样做,就是因为字符串常量是const char *,是一个imutable对象。虽然可以被转换为char *,但这样做无疑是有危险的。可以在上面的main函数里添加一个p[0] = ‘b’,马上会导致一个runtime error,如果是linux的话会告诉你是一个段错误。 指出一点:C++标准规定,字面字符串常量,像"abc",属于const char *。
----------
我觉得允许
char *p = "abc"; 这样的声明有点误导人的意思,但是好像教材上都没提出过这一点 刚刚运行了下面这段程序:
int main() { const int a = 8; int *p = const_cast<int*>(&a); *p = 9; cout << a << endl;
cout << *p << endl; cout << &a << endl; cout << p << endl; return 0;
} 结果是:8, 9,0x12FF7C,0x12FF7C 虽然地址一样,但是a还是8,并没有象lz说的那样a会变成9 地址应该是0x0012FF7C,写掉了2位
好久没上C/C++板块,还是有一些很不错的讨论
收藏先 我觉得这并不是一个很大的错误/问题,就像guqst(pop) 说的,仁者见仁,智者见智罢了。
在编译器设计上差异而已,对于应用并没有很大的影响。 我有个同事说,CSDN上太学究了,差不多说得就是这吧?
我个人认为这不是一个bug,理由如下:
首先可以确定的是,"abc"这样的一个字符串确实是放在常量数据区的,我们可以在初始化的时候这样进行:char *p = "abc";
这时候p指向的地址和函数地址在数值上很接近,这说明p指向了代码区。
这样做的原因是为了向下兼容,因为C89上没有const的概念,所以很多初始化的时候都是这么调用的,如果C++不允许这么做的话,在移植方面就会出现很多的错误,导致C程序员不愿意将改用C++。这是C++语言为了生存所做的妥协。
那么如下的调用呢?char p[] = "abc";
这没有任何问题,首先你声明了一个数组,然后将数组的大小定义成刚好能存下"abc"字符串,并且就真的存放了"abc"在里面,这时你的数组数存放在数据区的,并且已经在数据区分配了相应的空间,不论是全局的也好,还是自动的也好。
C++还有一个规则就是,非常量指针可以隐式转换成常量指针,而反之则需要显示转换。如:
char a[10];
const char *p = a; 这是没有问题的,但这么做只是说当我用p来操作这个地址中的数据时,我只想进行读操作,这样做相当于编译器帮你做了一部分代码检查工作,防止你在用p操作地址时错误的修改了地址中的数据。但p指向的地址并不是在代码区,这和char *p = "abc"有很大的区别。
反过来:
const char *p = "abc";
char *a = p; 这是不允许的,需要进行转换:char *a = const_cast<const char*>p;
说明这是我想要的强制转换,但如果这时你调用a[0] = 1;这样的操作,还是不会成功。
既然我们都能理解foo(char*)和foo(const char*)是怎么共存的了,那么如果按照如下调用:
try
{ throw "abc"; } catch (const char*) { cout << "catch(const char*)" << endl; } catch (char*) { cout << "catch(char*)" << endl; } 抛出的异常将永远被const char*截获,由于char*可以隐式转换成const char*,所以编译器会通知说,有一段异常处理代码永远不会运行到。
如果我们如下调用:
try
{ char *p = "abc"; throw p; } catch (char*) { cout << "catch(char*)" << endl; } catch (const char*) { cout << "catch(const char*)" << endl; } 增加一个指针的声明,我们就会发现,运行的效果是一样的。这就是为什么会让catch(char*)截获了的理由:
当异常抛出的时候,它首先走到了catch(char*)这个分之,它首先要进行初始化尝试,看是否可以将异常初始化成char*,由于以上所说,在初始化的时候,C++的编译器是允许将常量字符串赋值给一个非常量指针的,所以以上的异常将被第一个catch截获。
相同的例子:
foo(char *)
{ char *p = "abc"; } 当我们这么调用:foo("abc"),在函数调用时,不论是参数的传递,还是局部变量的初始化,都可以看作是存放在堆栈内的变量的初始化,所以常量字符串可以在初始化的时候传递给非常量字符串。
当然如下的声明更好:
foo(const char *)
{ const char *p = "abc"; } const是编译器的一个关键字,用来限制对其后声明的变量的操作。
bcc 5.82 输出是:
foo( char* ) catch( char* ) 好了好了,题外的东西有点太多了,还是回到题目中来,题目中的程序目的就是
想通过把多个字节合并为long型,然后写入内存来提高速度,其中longfill是用
"long"的值填充内存快,在填完了最后一个长字节之后返回一个指向下一次所要
填第一个长字的指针。结果速度的确提升了接近4倍。但是,程序忽略了两方面:
1,size初始值一定大于4么?
如果,size=3,程序就做了无谓的填充l,那么好了,我们这样:
void* memset(void *pv, byte b, size_t size) {
byte* pb = (byte*)pv; if (size >= sizeThreshold) { unsigned long l; l = (b << 8) | b; l = (l << 16) | l; pb = (byte*)longfill((long*)pb,l,size/4); size %= 4; } while (size– > 0) { *pb++ = b; } return (pv); } 我们可以设置一个threshold,来判断是否大于threshold再做下一步。 可是问题并没有结束:
2,long一定是4个字节么?
这一点程序没有做到,限制了其可移植性,于是我们再改:
void* memset(void *pv, byte b, size_t size) {
byte* pb = (byte*)pv; if (size >= sizeThreshold) { unsigned long l; size_t Ssize; l = 0; for (Ssize= sizeof(long);Ssize–>0;) l = (l<<8)|b; pb = (byte*)longfill((long*)pb,l,size/Ssize); size %= Ssize; } while (size– > 0) { *pb++ = b; } return (pv); } ps:以上的做法在pv不是很大的情况下效率不一定就高。
行业标准&潜规则–How to write solid and efficient code?(1)Posted by xiaokucha in Programming on December 14, 2006 先弄点老题,如下:
题目1:
作为开发团队的一员,你需要实现一些库函数提供给其他人使用。假设你实 现的一个函数原型如下: int DoSomeThing(char* pParam) 你们约定好参数pParam不能为NULL,但为了防止调用者错误传递NULL,你需 (a) if (!pParam) (b) if (!pParam) (c) if (!pParam) (d) if (!pParam) (e) if (!pParam) (f) assert(!pParam); 题目二:
下面函数实现,哪一个好,为什么? a. char Uptolower(char ch){ if(ch >= ‘A’ && ch <= ‘Z’) return ch+=‘a’-’A’; return -1; } b. char Uptolower(char ch){ assert(ch >= ‘A’ && ch <= ‘Z’); if(ch >= ‘A’ && ch <= ‘Z’) return ch+=‘a’-’A’; return ch; } c. char Uptolower(char ch){ assert(ch >= ‘A’ && ch <= ‘Z’); return ch+(‘a’-’A’); } 题目三: 下面的memset函数实现有什么问题? void *memset(void *pv, byte b, size_t size) l = (b << 8) | b; /* 用4个字节拼成一个long */ while (size– > 0) 题目四: 下面的代码用memset将三个局部变量置为0,请问可能会有什么问题? memset(&k, 0, 3*sizeof(int)); // 将i,j,k置为0 题目五: 定义结构如下: 题目六: 下面是C语言中两种if语句判断方式。请问哪种写法更好?为什么? if (n == 10) // 第一种判断方式 题目七: 下面的代码有什么问题? 题目八: 下面的代码有什么问题? … 题目九: char *_strdup( const char *strSource ) strcpy(str, strSource); 题目十: 下面的代码有什么问题?并请给出正确的写法。 以上都是一些很老的题目了,再一次翻出来就是温故,知新就是奢望了 最近很是无聊… 等2.0等得要死,宅男的我没有事情做,只好无聊到开始研究programming,发现这门tech实在是侮辱我的智商,限制我的思维发散能力。为什么要遵循ANSI,做出来的东西都是从别人的模子里面出来的。也许你会说这是为了统一行业标准,提高效率,你还会说你个sb不遵循也可以,你喝西北风去吧。皑皑,我真是选错了专业,天生理论物理天才的我竟然跟风选了cs,还tmd跑到了tju。唉,仰天一声长叹,不说也罢,既然走到这里,就tmd把programming进行到底。
TMD,9C连个p都不放看看人家tf最起码还有个公告,大陆这帮sb的经营理念阿,不从客户方面着想,还整个sb赏金活动,老子玩游戏是来happy的,不是tmd来挣你那点赏钱的。真的怒了,2.0不来,就将afk进行到底。
以下zz from tf:
其实一切的纷争,都是因为公告后发生的状况
相关单位会公告让大家知道,是因为要对玩家负责 按照制式的回答: 关于这个问题,请您注意官网公告 但是相关的单位选择不这样做! 在这边,希望能够先跟大家说明几点: 首先,社群团队在与玩家在讨论版上面的互动上,似乎并没有强制删文或者删除讨论串的状况 其次,台湾的玩家绝对不是次等公民,相信美方也不会这样想! 更完善的前置准备作业 语意不清引起大家猜测, 我们到底何时可以更新2.0.1的版本? 我们何时可以更新数据片的版本?? 一但有最新的消息,社群团队会尽快跟大家说的! |