[C/C++] 切割字串函數:strtok, Network mac address 分割

今天寫了 strtok 的範例:『如何分離網路 mac address』程式碼如下,大家一定會有疑問 strtok 第一次呼叫,第一參數輸入愈分離的字串,在 while 迴圈,則是輸入 NULL 呢?底下就來解析 strtok.c 的程式碼。
/*
*
* Author      : appleboy
* Date        : 2010.04.01
* Filename    : strtok.c
*
*/

#include "string.h"
#include "stdlib.h"
#include "stdio.h"

int main()
{
  char str[]="00:22:33:4B:55:5A";
  char *delim = ":";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok(str,delim);
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, delim);
  }      
  system("pause");
  return 0;
}
執行結果如下圖: strtok strtok.c 在 FreeBSD 7.1 Release 裡面路徑是 /usr/src/lib/libc/string/strtok.c,可以看到底下函式 __strtok_r
__strtok_r(char *s, const char *delim, char **last)
{
    char *spanp, *tok;
    int c, sc;

    if (s == NULL && (s = *last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        *last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = '\0';
                *last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}
大家可以看到,在第一次執行 strtok 時候,會針對傳入s字串每一個字進行比對,c = *s++; 意思就是 c 先設定成 *s,這行執行結束之後,會將 *s 指標加1,也就是字母 T -> h 的意思,這地方必須注意,如果第一個字母符合 delim 分隔符號,就會執行 goto cont;,如果不是,則會將 tok 指標指向 s 字串第一個位址,再來跑 for 迴圈找出下一個分隔字串,將其字串設定成 \0 中斷點,回傳 tok 指標,並且將s字串初始值指向分隔字串的下一個位址。 接下來程式只要繼續執行 strtok(NULL, delim),程式就會依照上次所執行的 s 字串繼續比對下去,等到 *last 被指向 NULL 的時候就不會在執行 strtok 了,我相信這非常好懂,微軟 Visual Studio 有不同的寫法: https://research.microsoft.com/en-us/um/redmond/projects/invisible/src/crt/strtok.c.htm
/* Copyright (c) Microsoft Corporation. All rights reserved. */

#include 

/* ISO/IEC 9899 7.11.5.8 strtok. DEPRECATED.
 * Split string into tokens, and return one at a time while retaining state
 * internally.
 *
 * WARNING: Only one set of state is held and this means that the
 * WARNING: function is not thread-safe nor safe for multiple uses within
 * WARNING: one thread.
 *
 * NOTE: No library may call this function.
 */

char * __cdecl strtok(char *s1, const char *delimit)
{
    static char *lastToken = NULL; /* UNSAFE SHARED STATE! */
    char *tmp;

    /* Skip leading delimiters if new string. */
    if ( s1 == NULL ) {
        s1 = lastToken;
        if (s1 == NULL)         /* End of story? */
            return NULL;
    } else {
        s1 += strspn(s1, delimit);
    }

    /* Find end of segment */
    tmp = strpbrk(s1, delimit);
    if (tmp) {
        /* Found another delimiter, split string and save state. */
        *tmp = '\0';
        lastToken = tmp + 1;
    } else {
        /* Last segment, remember that. */
        lastToken = NULL;
    }

    return s1;
}
微軟用了 strpbrk 來取代 for 迴圈的字串比對,但是整個流程是差不多的,大家可以參考看看,果然看 Code 長知識。
  • kile

    講解精闢,以前老師都說會用就好不用管它實際怎麼執行 經過你的解答大大了解他的運作了

  • Linroex

    好妙的寫法!

  • flash

    一點都不妙,
    s[-1] = ”;
    會破壞原來輸入參數的內容
    一點都不安全

  • 小肥肉

    終於看懂了strtok的用法了…謝謝你…小弟沒齒難忘~♥

  • helloworld

    安全的issue應該是還好
    input如果沒用const修飾的話就代表是會被修改的, 應該是個基本的默契了
    man也一再耳提面命了
    倒是__strtok_r應該是reentrant版的strtok

  • jason

    感謝分享

  • Pingback: [轉] [C/C++] 切割字串函數:strtok, Network mac address 分割 | GoMCU()