乾坤一笑 阅读(1980) 评论(54)
估计如果我一直在Windows下写程序、用VC写程序,绝对不会因为这一个小小的函数来写一篇blog。因为就算是出了差错,也在debug中随手改过来了,一点感觉和印象都没有。但是我是在做嵌入式开发,情形就不同了,我需要在Windows上用编辑器(SourceInsight或者ultraedit)写出源码,用编译器进行交叉编译,再烧到目标板上去看效果。这样一组操作的周期大概需要20分种左右(如果都先clean再build的话需要2个小时左右)。而平时的调试不仅没有VC这种IDE式的debug工具,就是连gdb这种console的单步调试工具也没有。一般没有致命错误,我是绝对不会动用JTAG之类的核子武器的,我会用Trace宏讲要看到的运行时信息打印到串口,当然更多的情况下,是用肉眼去判断。(不然完成一个项目估计1~2年是做不完了)——在这种情况下,如果概念不清、思路不清、库函数的细节不清楚,就会挂了。我这次用strcat()这个函数犯的错误就是个很好的例子。

好了,大家先看看这个程序,你能发现其中的bug吗?
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    #define MAX (100)
    #define START (10000)

    char buf[512], tmp[32];
    int a[MAX];
    int count = 0;
    int i;

    printf("%d %d %d \n",a[0],a[1],a[2]);
    //initialize an array for testing
    for ( i = START; i<START + MAX; ++i)
    {
        a[i - START] = i;
    }
  
    for ( i=0; i<MAX; ++i)
    {
        sprintf(tmp, "0x%.4X,", i);
        
        strcat(buf, tmp);
        if (count++ == 9)
        {
            //print result, output to standard io stream for samulation.
            printf("%s\n", buf);
            buf[0] = '\0';
            count = 0;
        }
        
    }
 
 return 0;
}
嘿嘿,这个bug可不简单,它会导致偶的系统驱动层的所有实体全部崩溃掉。偶是用的世界著名的A公司的OS,这个OS很多公司都在用。这段代码是偶写的代码的一个简化版。a[]这个数组是显示的缓冲区,我需要将它的内容trace到串口。因为a[]中的数据量很大,如果我极为频繁的访问串口的话,会导致串口阻塞,trace数据丢失,看不到我要看的数据。所以我把数据分了组,用strcat()来合并数据,一组一组的往串口发。

好了,背景介绍完了。看出bug了没有?如果没有看出来,我就要公布答案了! :)
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    #define MAX (100)
    #define START (10000)

    char buf[512], tmp[32];
    int a[MAX];
    int count = 0;
    int i;

    printf("%d %d %d \n",a[0],a[1],a[2]);
    //initialize an array for testing
    for ( i = START; i<START + MAX; ++i)
    {
        a[i - START] = i;
    }

    buf[0] = '\0'; ////////////////////////////////////////// here !
   
    for ( i=0; i<MAX; ++i)
    {
        sprintf(tmp, "0x%.4X,", i);
        
        strcat(buf, tmp);
        if (count++ == 9)
        {
            //print result, output to standard io stream for samulation.
            printf("%s\n", buf);
            buf[0] = '\0';
            count = 0;
        }
        
    }
 
 return 0;
}
问题的关键在于传递给strcat(dst,src)的两个参数都要是以'\0'结尾的。而我第一次传递给strcat()的第一个参数buf[]没有初始化,导致了buf的内容都非0,从而引起缓冲区溢出的错误。

先看看vc6.0中strcat的实现:
char * __cdecl strcat (
        char * dst,
        const char * src
        )
{
        char * cp = dst;

        while( *cp )
                cp++;                   /* find end of dst */

        while( *cp++ = *src++ ) ;       /* Copy src to end of dst */

        return( dst );                  /* return dst */

}
在find end of dst这一步,程序会从dst的起始地址开始,去找一个'\0',并且只有找到这个'\0',代码才会结束。Okey,问题明了了。由于我没有初始化buf[],而系统随便分配一块内存给它,里面可能还不为0,它就不停的跑下去了。就算它停了下来,第二步的copy src to end of dst也会接着写下去,如果此刻超出了buf的范围,就会把src中的内容写到不该写的位置上去,这样就破坏了其他的进程的代码和数据,系统就崩溃掉了。

反过来想,当系统崩溃的时候,又有谁一下子能想到是buf没有初始化引起的呢?:)  值得一提的是,这段代码在Mingw上不初始化buf也不会出错的,就是说,由于编译器实现不同,可能MingW在分配buf的时候已经替它做了初始化。所以在MingW上编译我们第一段代码,它也跑的很好,不会出错。但是如果你来做移植工作,把MingW上的一个工程移植到其他平台,系统就挂掉了,再也跑不起来,你能查到这个由于strcat()参数没有初始化的bug么?

呵呵,所以coder的责任重大,不写代码没什么问题,如果写了错误的代码就会给维护的人员造成很大的压力了。所以,一个好的coder平时就要对自己有至少两点要求:第一,要对库函数实现有清晰的认识,最好能了解几种不同实现的代码,要知道他们的实现原理,知其然并且知其所以然;第二,平时工作就要多用几种不同的编译器,这样才好及时的发现不能跨平台的问题,及时去解决。Coder任重而道远,虽不能妄自尊大,也不要妄自菲薄呀!:)


----------------------------------------
吃完饭回来,再补充两句:不要以为strcat()不作边界检查、或者不强制初始化是C语言的不足。没有任何一个有实用价值的操作系统实现会去强制执行边界检查或者缓冲区溢出的错误的,不然它在市场上就没有竞争力。以下给出几种strcat的实现,希望有参考价值:

Linux 0.11的实现:
//////// Linux 0.11 ///////////
extern inline char * strcat(char * dest,const char * src)
{
__asm__("cld\n\t"
 "repne\n\t"
 "scasb\n\t"
 "decl %1\n"
 "1:\tlodsb\n\t"
 "stosb\n\t"
 "testb %%al,%%al\n\t"
 "jne 1b"
 ::"S" (src),"D" (dest),"a" (0),"c" (0xffffffff));
return dest;
}
Linux 2.4.26的实现:
///// LINUX 2.4.26 /////

#ifndef __HAVE_ARCH_STRCAT
/**
 * strcat - Append one %NUL-terminated string to another
 * @dest: The string to be appended to
 * @src: The string to append to it
 */
char * strcat(char * dest, const char * src)
{
 char *tmp = dest;

 while (*dest)
  dest++;
 while ((*dest++ = *src++) != '\0')
  ;

 return tmp;
}
#endif
GNU glibc的实现:
/* Copyright (C) 1991, 1997, 2003 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <string.h>
#include <memcopy.h>

#undef strcat

/* Append SRC on the end of DEST.  */
char *
strcat (dest, src)
     char *dest;
     const char *src;
{
  char *s1 = dest;
  const char *s2 = src;
  reg_char c;

  /* Find the end of the string.  */
  do
    c = *s1++;
  while (c != '\0');

  /* Make S1 point before the next character, so we can increment
     it while memory is read (wins on pipelined cpus).  */
  s1 -= 2;

  do
    {
      c = *s2++;
      *++s1 = c;
    }
  while (c != '\0');

  return dest;
}
libc_hidden_builtin_def (strcat)

VC 7.1的实现:
        page    ,132
        title   strcat - concatenate (append) one string to another
;***
;strcat.asm - contains strcat() and strcpy() routines
;
;       Copyright (c) Microsoft Corporation. All rights reserved.
;
;Purpose:
;       STRCAT concatenates (appends) a copy of the source string to the
;       end of the destination string, returning the destination string.
;
;*******************************************************************************

        .xlist
        include cruntime.inc
        .list


page
;***
;char *strcat(dst, src) - concatenate (append) one string to another
;
;Purpose:
;       Concatenates src onto the end of dest.  Assumes enough
;       space in dest.
;
;       Algorithm:
;       char * strcat (char * dst, char * src)
;       {
;           char * cp = dst;
;
;           while( *cp )
;                   ++cp;           /* Find end of dst */
;           while( *cp++ = *src++ )
;                   ;               /* Copy src to end of dst */
;           return( dst );
;       }
;
;Entry:
;       char *dst - string to which "src" is to be appended
;       const char *src - string to be appended to the end of "dst"
;
;Exit:
;       The address of "dst" in EAX
;
;Uses:
;       EAX, ECX
;
;Exceptions:
;
;*******************************************************************************

page
;***
;char *strcpy(dst, src) - copy one string over another
;
;Purpose:
;       Copies the string src into the spot specified by
;       dest; assumes enough room.
;
;       Algorithm:
;       char * strcpy (char * dst, char * src)
;       {
;           char * cp = dst;
;
;           while( *cp++ = *src++ )
;                   ;               /* Copy src over dst */
;           return( dst );
;       }
;
;Entry:
;       char * dst - string over which "src" is to be copied
;       const char * src - string to be copied over "dst"
;
;Exit:
;       The address of "dst" in EAX
;
;Uses:
;       EAX, ECX
;
;Exceptions:
;*******************************************************************************


        CODESEG

%       public  strcat, strcpy      ; make both functions available
strcpy  proc
        push    edi                 ; preserve edi
        mov     edi,[esp+8]         ; edi points to dest string
        jmp     short copy_start

strcpy  endp

        align   16

strcat  proc

        .FPO    ( 0, 2, 0, 0, 0, 0 )

        mov     ecx,[esp+4]         ; ecx -> dest string
        push    edi                 ; preserve edi
        test    ecx,3               ; test if string is aligned on 32 bits
        je      short find_end_of_dest_string_loop

dest_misaligned:                    ; simple byte loop until string is aligned
        mov     al,byte ptr [ecx]
        add     ecx,1
        test    al,al
        je      short start_byte_3
        test    ecx,3
        jne     short dest_misaligned

        align   4

find_end_of_dest_string_loop:
        mov     eax,dword ptr [ecx] ; read 4 bytes
        mov     edx,7efefeffh
        add     edx,eax
        xor     eax,-1
        xor     eax,edx
        add     ecx,4
        test    eax,81010100h
        je      short find_end_of_dest_string_loop
        ; found zero byte in the loop
        mov     eax,[ecx - 4]
        test    al,al               ; is it byte 0
        je      short start_byte_0
        test    ah,ah               ; is it byte 1
        je      short start_byte_1
        test    eax,00ff0000h       ; is it byte 2
        je      short start_byte_2
        test    eax,0ff000000h      ; is it byte 3
        je      short start_byte_3
        jmp     short find_end_of_dest_string_loop
                                    ; taken if bits 24-30 are clear and bit
                                    ; 31 is set
start_byte_3:
        lea     edi,[ecx - 1]
        jmp     short copy_start
start_byte_2:
        lea     edi,[ecx - 2]
        jmp     short copy_start
start_byte_1:
        lea     edi,[ecx - 3]
        jmp     short copy_start
start_byte_0:
        lea     edi,[ecx - 4]
;       jmp     short copy_start

;       edi points to the end of dest string.
copy_start::
        mov     ecx,[esp+0ch]       ; ecx -> sorc string
        test    ecx,3               ; test if string is aligned on 32 bits
        je      short main_loop_entrance

src_misaligned:                     ; simple byte loop until string is aligned
        mov     dl,byte ptr [ecx]
        add     ecx,1
        test    dl,dl
        je      short byte_0
        mov     [edi],dl
        add     edi,1
        test    ecx,3
        jne     short src_misaligned
        jmp     short main_loop_entrance

main_loop:                          ; edx contains first dword of sorc string
        mov     [edi],edx           ; store one more dword
        add     edi,4               ; kick dest pointer
main_loop_entrance:
        mov     edx,7efefeffh
        mov     eax,dword ptr [ecx] ; read 4 bytes

        add     edx,eax
        xor     eax,-1

        xor     eax,edx
        mov     edx,[ecx]           ; it is in cache now

        add     ecx,4               ; kick dest pointer
        test    eax,81010100h

        je      short main_loop
        ; found zero byte in the loop
; main_loop_end:
        test    dl,dl               ; is it byte 0
        je      short byte_0
        test    dh,dh               ; is it byte 1
        je      short byte_1
        test    edx,00ff0000h       ; is it byte 2
        je      short byte_2
        test    edx,0ff000000h      ; is it byte 3
        je      short byte_3
        jmp     short main_loop     ; taken if bits 24-30 are clear and bit
                                    ; 31 is set
byte_3:
        mov     [edi],edx
        mov     eax,[esp+8]         ; return in eax pointer to dest string
        pop     edi
        ret
byte_2:
        mov     [edi],dx
        mov     eax,[esp+8]         ; return in eax pointer to dest string
        mov     byte ptr [edi+2],0
        pop     edi
        ret
byte_1:
        mov     [edi],dx
        mov     eax,[esp+8]         ; return in eax pointer to dest string
        pop     edi
        ret
byte_0:
        mov     [edi],dl
        mov     eax,[esp+8]         ; return in eax pointer to dest string
        pop     edi
        ret

strcat  endp

        end



-------------
乾坤一笑 写于2005年6月18 日  转载请标明出处和原文链接


评论列表
pAnic
re: 我用错了strcat()
其实这只是违背了一个简单的原则:
变量使用前要初始化。
pAnic
re: 乾坤一笑
事实上充分初始化还是必要的,因为这里用strcat,所以初始化第一个字节即可,但是假如用strncpy呢?
在代码的最初阶段,有必要用完整的,安全的,但是效率不高的手法对数据进行必要的初始化,只有当优化时,才有必要把那些影响效率的多余的代码清理掉。
还是那句话:“过早优化是一种罪”
乾坤一笑
re: 我用错了strcat()
/////// vc6.0 source ////////////

char * __cdecl strncpy (
        char * dest,
        const char * source,
        size_t count
        )
{
        char *start = dest;

        while (count && (*dest++ = *source++))    /* copy string */
                count--;

        if (count)                              /* pad out with zeroes */
                while (--count)
                        *dest++ = '\0';

        return(start);
}

strncpy更加不需要对dest进行初始化了。虽然dest和source内含的字符长度相等的时候不会在dest的末尾加0,但这也正好证明了我的观点:要对库的实现很了解,就不会出错了。不要想当然的乱用库函数。
pAnic
re: 我用错了strcat()
字符串是个麻烦事~
清风雨
不知道可不可以这么说
在C或C++的函数库里,字符串的概念一定是一个以'\0'结尾的字符,而且'\0'和它以前的内容一起构成字符串。

在MSDN里面常看到一个说法叫NULL结尾的字符串,也就是说可以有非'\0'结尾的字符串。但是,看看C里,定义、使用,都是:字符串是由一组联系的字符以'\0'结尾构成的。
乾坤一笑
to 清风雨:

你看MSDN看的很细,可惜的是没有正确理解“NULL结尾的字符串”的含义。MSDN上所谓的Null-terminated string 其实就是着的是“以'\0'结尾的字符串”。要理解这个概念要说到三个方面:
一、NULL是什么?
     纵观VC6.0的库代码,有22处定义了NULL宏,但是所有定义都是一模一样的:

#undef  NULL
#ifndef NULL
#ifdef __cplusplus
#define NULL    0
#else  /* __cplusplus */
#define NULL    ((void *)0)
#endif  /* __cplusplus */
#endif  /* NULL */

二、'\0'是什么?
'\0'是C语言定义的用'\'加8进制ASCII码来表示字符的一种方法,'\0'就是表示一个ASCII码值为0的字符。同样的,你用'\x0'来表示也可以,这是用16进制的ASCII码来表示,虽然很不常见。

三、0值的意义何在?
0是一个整数。由于各种标准转换,0可以被用于作为任意整型、浮点类型、指针。0的类型将用上下文来确定。典型情况下0被表示为一个适当大小的全零二进制位的模式。所以,无论NULL是定义为常数0还是((void *)0)这个零指针,NULL都是指的是0值,而不是非0值。而字符的'\0'和整数的0也可以通过转型来相互转换。

综上所述,NULL就是指的是0值,Null-terminated string就是以0值结尾的string,也就是以'\0'。其实C语言中的变量只有4中char、int、float、poiner四大类。所谓的字符串只是一堆char的后面加上一个0而已。

清风雨
或许是我没说清楚
我想想微软既然那么说。也就表示,可以比如用其它的结束标识符号表示字符串。(不知道微软是不是这个意思)
但是,事实是:字符串是由一组有联系的字符以'\0'结尾构成的。
所以,你strcap时,要传递的buf,应该是一个字符串,也就是必须以'\0'结尾。那么我就没有细看你的文章了。也就表示每种实现其实都是结果一致的。
正确的一致,错误,不一定要错的一样。^_^

呵呵,那就是说,我个人觉得这贴的讨论意义不大。(仅仅是个人见解)

我比较懒惰,所以不提倡详尽到要看每个库的细节实现。而赞同透明使用,各司其职。
乾坤一笑
to 清风雨 :
晕倒!看来咱们俩之间确实误会不小。偶表达能力差,又不善解人意。:p

规矩是人定的,当然可以有其他的方法表示字符串。历史上FORTRAN一脉的强编译型语言表示字符串大体有两种方法:1>类C语言使用'\0'来终止字符数组;2>pascal之类的语言,用一个表示字符串长度的量捆绑到字符串上。两者各有千秋。但总体来说在于使用的人。

你们没有仔细看偶的文章,自然觉得意义不大。我的这篇blog是借题发挥,寓意并非研究strcat()这么肤浅。当然,寓意是什么,我相信仔细看过这篇文章的人都知道。

开发领域不同,自然对软件工程师的要求也自然不一样:本文针对的是系统级开发的软件工程师说的,在这个领域里效率是第一位,效率和安全要兼顾。至于上层的开发人员,我也认为没必要了解库细节——专注做业务逻辑就可以了,合理的软件构架才是他们需要关心的。
清风雨
是我不知道pascal的表示
一直很奇怪微软为什么那么说。 而且有个同事把字符串的概念也混了。
而又觉得混了,在C里面行不通。

所以,跑过来说了一下,好像字符串必须以'\0'结尾。

我看上面的VC6、gcc、linux的实现是没有差异的。—— 俺可能对VC71这种编译器的优化太信任了。而且太过相信0.0001ms和0.1ms如果不是频繁累积的,对程序员或者用户都没有意义。

倒是《more effective c++》有个结论,我很赞同:不同的库实现之间的性能是有差异的,选择适当的库,对你程序的性能会有帮助。(但,并不一定要清楚内部实现,毕竟没有人愿意去把所有的工作自己再做一般,而且也没有那个精力)

是我表达能力不好!

俺不是来唱反调的,只是发表一下个人的读后感。
乾坤一笑
to 清风雨 :
欢迎发表读后感!本站特别欢迎发表读后感!Welcome to my blog!:p

我之所以贴出那些实现也是想说,这些实现都一样的。这些实现都没有对strcat(dst,src)中的dst参数进行检查就开始操作了。所以我们要小心。

另外,0.1ms对我来说是至关重要的,0.01ms对我来说也是不能小觑的。这段代码的原本用于trace一个LCD的驱动的,我要trace出每一个点的值来确认LCD buffer是否被其他因素干扰致使画面局部显示不正常。假设每一个点要delay 0.1ms的话,整个LCD有128*160个点,就要delay 0.1ms * 128 * 160 = 2048ms,想想看,当你的画面每刷新1次,就需要等待2048ms是一个什么样的情形?:) 所以我说开发领域不同要求也不同,是真心话。我没有看过任何一个LCD厂商的驱动参考设计中使用 x/2的方法来计算x的一半是多少,它们都是用x>>2来计算,而这点效率的差别对普通上层开发来说是无关紧要的,但是对于底层驱动开发来说体现出的就是整个产品品质的差异了。
清风雨
呵呵,不继续下去了
如果不是频繁累积的 ^_^ (其实,大多问题大家其实都是一致的,关键看怎么引导和一个度的问题。)

顺便,就像指针检查一样,除了非NULL,C/C++基本无能为力。这就是C/C++所要做的,它把责任交给程序员,让程序员获取自己想要的性能和安全(不检查,可以相对减少开销:内存+运算)。
字符串也是一样,除了往后面找'\0',它没有别的办法。
或者,要么就传递数组,数组是要有长度的(免得挑刺,数组引用,或者内存块指针+大小 这里都算)。
不知道我的顺便是否正确(个人是这么认为的,没有见过什么C/C++的设计者这么说,或许有大致相近的说法,但总也不能确定就是这样。所以,也就只是个人看法)。
乾坤一笑
re: 我用错了strcat()
okey, okey.
顺便介绍一下PASCAL的字符串用法:
var s : string;
pascal的字符串的索引是从1开始的,s[0]是什么?s[0]就是s这个字符串的长度。由于最早(dos下)字符串都是中的每个组成单元都是一个字符,也就是8位的char,所以s[0]的大小受陷于2^8=256,也就是说早起的pascal的string的长度不能超过256。不过到了windows之后就好了,object pascal把字符串前导字节增加为32位,也就是说字符串的最大长度为2^32=4294967296,所以一般来说这个字符串长度还是够用了。Null-terminated字符串的好处是字符串不受长度的限制,这对于将底层操作统一为串操作有极大的好处(比如Linux下把所有的设备都映射为文件,可以用串操作函数来处理);有长度的字符串也有很多好处,因为它得到整体字符串长度的开销很小,所以它很容易就获得了做静态检查、越界检查的能力。
清风雨
长见识了! 谢谢!
以前一直不知道Null-terminated字符串是什么意思。
现在总算明白了。

非常感谢!
jzhang
如果楼主使用lint检查您的c/c++代码
这样的问题根本就不会出现,也用不着费尽心机的去调试了。推荐使用lint检查代码.
乾坤一笑
to jzhang :
并非所有的C编译器都提供lint的,偶用的C编译器组来gdb之类的工具都没有。在特定的硬件平台下,编译器是没法自由选择的,商用的嵌入式系统尤是如此。:)
gggg
这里其实不必要用strcat()
增加个
    int offset=0;
即可:

    for ( i=0; i<MAX; ++i)
    {
        sprintf(buf + offset, "0x%.4X,", i);
        
        offset += 7;
        
        if (count++ == 9)
        {
            //print result, output to standard io stream for samulation.
            printf("%s\n", buf);
            offset = 0;
            count = 0;
        }
        
    }
aydge
re: 我用错了strcat()
使用变量先初始化是基本的要求吧?
cpunion
re: 我用错了strcat()
我觉得这算是标准委员会那些人的失误(库函数原型是他们定的吧?)。

这些函数原型就有些问题,我是很难明白返回一个char*有何意义,不如返回一个int或size_t作为长度来得实在,这个长度在内部循环时就已经得到了,作为返回值可以省得我们再获取长度,也方便我们直接加个\0,还不需要库函数帮我们把剩余缓冲区填满\0这种低效的作法。

char* strncat (char* dst, const char* src, size_t len);
改为:
int strncat (char* dst, const char* src, size_t len);

返回个char*作什么?鼓励坏习惯吗?(strcat(strcat(buf,"abc"), "def");这种坏习惯)。
cpunion
re: 我用错了strcat()
当然了,你原来使用的那个错误是值得狠狠批评的,C字符串操作函数都会去找\0结尾的,所以初始化时刻不能忘了。。。。如果缓冲区不是很大,我觉得用memset全部初始化掉也没什么,效率低一点,不过省了很多事。当然我自己使用时不会这么干 :-)
满头大汗
re: 我用错了strcat()
看了两个低手在这里讨论,真搞笑。用字符串不初始化,这种低级错误还好意思写。你说你看库的实现,你才看了几个
操作系统啊?不同的操作系统实现都不一样,关看个VC和glibc就以为很了解了。居然也敢说标准委员会失误,你是不是
只用过Windows平台啊。  
  The strcat() function appends a copy of the string pointed to by the s2 parameter (including the
  inating null byte) to the end of the string pointed to by the s1 parameter. The initial
  byte of s2 overwrites the null byte at the end of the string pointed to by s1. 
  When operating on overlapping strings, the behavior of this function is unreliable. 
  这是Tru64下关于strcat的说明,写得这么详细了,还用得着你在这里猜啊。
乾坤一笑
to 满头大汗:
真是大牛!最起码比偶牛,哈哈。
乾坤一笑
to 满头大汗:
大牛!你有没有什么书、blog、或专场论坛之类的让偶们学习一下?
to 满头大汗
re: 我用错了strcat()
把你的blog贴出来吧,我们保证用口水淹没它。用过True64就牛啦,你没见过的多了,哪有像你这样招摇的“高手”。


兄弟们,偶是小三 :)
漫天飞舞
大家这么热闹,我也凑一个,不过就不说其他的了,很明确:

大家这么热闹,我也凑一个,不过就不说其他的了,很明确:如果是底层开发,就要效率第一了,如果是应用层开发,那么就代码风格第一了。再争吵的就有点。。。。。sb的干活了!

btw:一笑这里估计大意了吧,你也看看你的这个‘bug’,我就不说了。
‘我没有看过任何一个LCD厂商的驱动参考设计中使用 x/2的方法来计算x的一半是多少,它们都是用x>>2来计算,而这点效率的差别对普通上层开发来说是无关紧要的,但是对于底层驱动开发来说体现出的就是整个产品品质的差异了。’

一笑
to 漫天飞舞:
汗!你看的真细。叶子,以后专职帮偶校稿好了~~ :D 
---
正确的是x/2 可以用右移一位来得到, 即x>>1
我最菜
to 翁翁
是这样
char buff[512] = {0}
一笑
re: 我用错了strcat()
最简单的,vc安装盘目录下有个CRT的目录,里面就是vc自带的C/C++库的源代码。
lance
re: 我用错了strcat()
还真是来,可算开眼了,呵呵
那Linux和GNU libc的那些从哪儿能下到啊?最好说的具体点,让我能点击下载,呵呵,实在是太菜了~~
谢谢昂
luomingjian
re: 我用错了strcat()


这些函数原型就有些问题,我是很难明白返回一个char*有何意义,

char* strncat (char* dst, const char* src, size_t len); 
改为: 
int strncat (char* dst, const char* src, size_t len); 

返回个char*作什么?鼓励坏习惯吗?(strcat(strcat(buf,"abc"), "def");这种坏习惯)。

--------------------------
同问!
-------------------------
Joel也提到过这个问题,<Joel说软件> P5-P9。但我一直相信写库代码的人是牛,不会仅仅为了(strca(strcat(buf,"abc"), "def")而改变返回值类型,他的本意就是是什么?盼指点!
luomingjian2005@yahoo.com.cn







luomingjian
re: 我用错了strcat()
顺便提一下,俺是搜索C++98标准和C99 而发现的这块宝地。
乾坤一笑有中文文档啊,英文的也行?
中文的俺可以一目十行,英文的俺只能十目一行:)

luomingjian2005@yahoo.com.cn
秋枫
re: 我用错了strcat()
取1/2是右移一位啊,怎么移两位。。。

关于关注库函数的实现,个人觉得,没必要争论。有的时候,底层了解的太多了,反而更糊涂了。

标准化的目的,也是为了减少人对底层的关注。

本人系伪程序员,就是鉴定完毕,准予结贴。。。。
透明de面具
re: 我用错了strcat()
luomingjian


这些函数原型就有些问题,我是很难明白返回一个char*有何意义, 

char* strncat (char* dst, const char* src, size_t len);  
改为:  
int strncat (char* dst, const char* src, size_t len);  

返回个char*作什么?鼓励坏习惯吗?(strcat(strcat(buf,"abc"), "def");这种坏习惯)。 

-------------------------- 
同问! 
------------------------- 
Joel也提到过这个问题,<Joel说软件> P5-P9。但我一直相信写库代码的人是牛,不会仅仅为了(strca(strcat(buf,"abc"), "def")而改变返回值类型,他的本意就是是什么?盼指点! 


这个的目的就只是为了串式表达式的需要。没有别的了
去看看《高质量c++编程指南》这个书,里面有提到

strcpy 能把strSrc 的内容复制到strDest,为什么还要char * 类型的返回值? 
答:为了实现链式表达式。
    例如   int length = strlen( strcpy( strDest, "hello world") );


这样是很坏的习惯莫?我不这样看。

如果需要高效的情况下,
int length = strlen( strcpy( strDest, "hello world") );

应该会比

strcpy(strDest, "hello world");
int length = strlen(strDest);

要略快一些
sd
请教一下.
"使用 x/2的方法来计算x的一半是多少,它们都是用x>>2来计算,"
10的二进制码:00001010 右移三位,10>>3,10/8应该是:1.25是不?
但是移位后的二进制码等于1.25吗?
请教一下.
Prudence
re: 我用错了strcat()
建议楼主养成局部变量数组初始化的习惯,如果是全局数组的话就没必要初始化了,默认就是以0做为初值,这也是为什么一些C++书上讲的局部变量编译器不初始化(其实这里是错的,编译一样初始化的只是以0xCC),全局变量初始化为0。
void main()
{
  int i;
  cout<<hex<<i;
}
观察下结构是否为0xCCCCCCCC
总的一局话,C++给局部变量以0xCC初始化,全局变量0初始化。
所以你如果在函数中声明数组的话,记住一定要调用memset函数,给该字符串数组进行初始化,这是一种习惯,一定要养成。
因为局部变量是在栈上分配的每个字节c++都是以0xCC来初始化的,并非0,而字符串的结尾是以0做为标志的,当然Unicode码不是这样的(Unicode码是在字符串的前四个字节编译器事先放入字符串长度,你可以随便找个VB的程序调试下)。
18199625
re: 我用错了strcat()
其他的字符串操作也是需要注意一点的,例如sprintf().在有些时候对未初始化的字符串进行操作会导致编译的不通过.且如果不是集成环境下的编程,还不会给出出错的行数,只有通过记忆来查找......
fireswood
re: 我用错了strcat()
今天我也是用strcat出了错才来这里

大家好:)

char_file=(char*)malloc(sizeof(char)*n);

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

{

char_file[i]=*(argv0+i);

}//char_file的n个char字符空间被装满~

char_file[i]='\0';

strcat(char_file,"abc...");

程序在VC中可以编译通过,然后我拿去linux gcc编译执行

提示"段错误",后来知道了linux下strcat要求strcat(dst,src)dst大小可以容纳src,我的程序可以看出是将src加在dst(malloc产生的空间)后面,故而出错

但是请问,为什么VC中就可以执行呢?

个人猜测和weindows和linux堆管理策略有关,具体为何不知道。



还有看了楼主的帖子Linux 2.4.26 和GNU glibc实现不同

有点迷茫:

既然Linux 2.4.26 已经实现了strcat

然后我们如果安装GNU glibc的话,

我们编程的话,那编译器会使用那种实现?

(我对GNU glibc的理解是glibc是gcc编译器使用的库函数,不知对不?)



还有个问题*_*

char_file[i]='\0';

这一句如果没有,

那么char_file通常会为c:/x/x/    abc...

有的话是c:/x/x/abc...,没有那几个空格

(c:/x/x/ 代表一个路径,abc...代表字符串)

请问为何





*_*

fireswood
re: 我用错了strcat()
今天我也是用strcat出了错才来这里

大家好:)

char_file=(char*)malloc(sizeof(char)*n);

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

{

char_file[i]=*(argv0+i);

}//char_file的n个char字符空间被装满~

char_file[i]='\0';

strcat(char_file,"abc...");

程序在VC中可以编译通过,然后我拿去linux gcc编译执行

提示"段错误",后来知道了linux下strcat要求strcat(dst,src)dst大小可以容纳src,我的程序可以看出是将src加在dst(malloc产生的空间)后面,故而出错

但是请问,为什么VC中就可以执行呢?

个人猜测和weindows和linux堆管理策略有关,具体为何不知道。



还有看了楼主的帖子Linux 2.4.26 和GNU glibc实现不同

有点迷茫:

既然Linux 2.4.26 已经实现了strcat

然后我们如果安装GNU glibc的话,

我们编程的话,那编译器会使用那种实现?

(我对GNU glibc的理解是glibc是gcc编译器使用的库函数,不知对不?)



还有个问题*_*

char_file[i]='\0';

这一句如果没有,

那么char_file通常会为c:/x/x/    abc...

有的话是c:/x/x/abc...,没有那几个空格

(c:/x/x/ 代表一个路径,abc...代表字符串)

请问为何





*_*

zzq
re: 我用错了strcat()
我看了所有的帖子。用了一个多小时,觉得强人好多啊,
顶!
whh
re: 我用错了strcat()
用'\0'来结束字符串,简直愚蠢至极,不知道当初怎么定义的。
乾坤一笑
to whh //re: 我用错了strcat()
我觉得这个定义非常的明智。
一笑
to future //re: 我用错了strcat()
如果数组比较大的话,用memset太费时间。
again
re: 我用错了strcat()
第一次看的时候能够看出这个问题。但是自己写代码的时候就会犯这样的问题了。
纪念一下.
solo
re: 我用错了strcat()
谢谢你的这篇BLOG~~~获益匪浅啊!!
he
re: 我用错了strcat()
也是linux gcc 时用strcat时出错:段错误。
看了很久,学习了:)
伪系统程序员
re: 我用错了strcat()
看了大牛们的发言,只能叫自己伪系统程序员了!

大牛在热闹讨论问题的时候,小伪才刚刚工作!受教了!!!
1
re: 我用错了strcat()
cpunion
我觉得这算是标准委员会那些人的失误(库函数原型是他们定的吧?)。 

这些函数原型就有些问题,我是很难明白返回一个char*有何意义,不如返回一个int或size_t作为长度来得实在,这个长度在内部循环时就已经得到了,作为返回值可以省得我们再获取长度,也方便我们直接加个\0,还不需要库函数帮我们把剩余缓冲区填满\0这种低效的作法。 

char* strncat (char* dst, const char* src, size_t len); 
改为: 
int strncat (char* dst, const char* src, size_t len); 

返回个char*作什么?鼓励坏习惯吗?(strcat(strcat(buf,"abc"), "def");这种坏习惯)。
----------------------------------------------------

根据我的理解,楼上的函数定义是有问题的,请问返回值在哪里?
anonymous
re: 我用错了strcat()
这么简单的bug一眼就看出来了,楼主怎么还犯这种错
Soros
re: 我用错了strcat()
都是一些很小的细节,养成良好的编程习惯才能避免这样的错误;
良好编程习惯的养成又需要大量的编程与出错次数,出错多了也就会修正自己的习惯;也许这就是一些人说“20年的编程经历才敢说精通C语言”的原因吧~
C语言真的是让人又爱又恨~

发表评论
切换编辑模式