【问题标题】:Strange behavior of String tokenizer in CC中字符串标记器的奇怪行为
【发布时间】:2012-05-03 05:26:28
【问题描述】:

我编写了以下程序来解析多个目录名称的路径

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

char *
tokenizer(char *path, char **name){
  char s[300];
  char *buffer;
  memcpy(s, path, strlen(path)+1);
  printf("%s\n",s);    // PROBLEM
  int i=0;
  while(s[i] == '/'){
    i++;
  }
  if (i == strlen(path)){
    return NULL;
  }
  *name = strtok_r(s, "/", &buffer);
  return buffer;
}

int main(void){
  char str[300];
  char *token, *p;
  scanf("%s",str);
  p = tokenizer(str, &token);
  if (p != NULL)
    printf("%s\n",token);
  else
    printf("Nothing left\n");
  while((p=tokenizer(p, &token)) != NULL){
    printf("%s\n",token);
  }
}

上述程序的输出

Input: a/b/c
Output: a/b/c
a/b/c
a
b/c
b
c
c

如果我评论标有问题的行

Input: a/b/c
Output: Some garbage value

有人能解释一下这种奇怪行为的原因吗?

注意: 我已经意识到s 是一个堆栈分配变量,它在函数main() 中不再存在,但是为什么当我使用printf() 时程序可以工作?

【问题讨论】:

  • 如果我编译你的程序,我会在最底部的while 行中收到警告。最好先调查一下。
  • @MrLister 我在编译程序时没有收到任何警告。您为 gcc 编译器指定了哪些选项?
  • 为什么是 memcpy 而不是 strcpy? (无论如何你都依赖 strlen)
  • @gibraltar gcc 4.3 without options给了我test.c:31: warning: passing argument 2 of ‘tokenizer’ from incompatible pointer type
  • @MrLister 我已经纠正了错误。很抱歉。

标签: c string pointers strtok


【解决方案1】:

除了极客龙所说的:

strtok_r的第三个参数用错了,有两种方式:
1. 首次调用前应初始化为NULL。
2.不应该以任何方式使用它(你将它返回给调用者)。它只能传递给另一个strtok_r 调用。

【讨论】:

    【解决方案2】:

    您正在返回一个指向堆栈分配字符串的指针(buffer 指向s);在tokenize 返回后,s 的内存不再有意义。

    【讨论】:

      【解决方案3】:

      你不能这样做

      char s[300];
      char *buffer;
      ...
      *name = strtok_r(s, "/", &buffer);
      return buffer;
      

      这里的buffer 是指向s[300] 位置的指针。 s[300]是函数调用时分配在栈上的函数局部变量,函数返回时销毁。 所以你没有返回一个有效的指针,你不能在函数之外使用那个指针。

      【讨论】:

        【解决方案4】:

        除了观察到您正在返回指向局部变量的指针之外,我认为值得注意的是,您的 tokenizer 几乎 100% 毫无意义。

        tokenizer 所做的大部分工作是在调​​用 strtok_r 之前跳过任何前导 / 字符——但你将“/”作为分隔符传递给 strtok_r,这将自动跳过任何单独的前导分隔符。

        相当简单的代码足以打印出没有分隔符的路径组件:

        char path[] = "a/b/c";
        char *pos = NULL;
        
        char *component = strtok_r(path, "/", &pos);
        while (NULL != component) { 
            printf("%s\n", component);
            component = strtok_r(NULL, "/", &pos);
        }
        

        【讨论】:

        • 我知道如何标记字符串。我知道我可以采用上述实现。我在问为什么我的程序在 printf 的情况下表现得很奇怪。而tokenizer只是一个测试程序。我正在使用类似 linux 的目录结构实现一个文件系统,我需要一个一个地遍历目录才能到达最终的 inode。
        • @gibraltar:那么,如果您知道如何标记字符串,为什么还要询问您知道具有未定义行为的代码的行为?
        • 我的问题是为什么程序在 printf() 的情况下表现正确?
        • @gibraltar:“未定义的行为行为,在使用不可移植或错误程序结构或错误数据时,本国际标准对此没有要求”这可能(和经常这样做)包括看起来正常工作,至少在一些定义不明确的条件下。
        【解决方案5】:

        试试这个:

        char*
        token(char * path, char ** name){
        
            static char * obuffer = NULL;
            char * buffer = NULL, * p, * q;
        
            if(path == NULL) {
                buffer = realloc(buffer, strlen(obuffer) + 1);
                p = obuffer;
            } else {
                buffer = malloc(257);
                p = path;
            }
        
            if(!buffer) return NULL;
            q = buffer; 
        
            if(!p || !*p) return NULL;
        
            while(*p != '\0') {
                  if(*p == '/') { 
                    p++; /* remove the / from string. */
                    break;
                  }
                  *q ++ = *p++;
            }
        
            *q ++ = '\0';
            obuffer = p;
            *name = buffer;
        
            return buffer;
        }
        
        int main(void)
        {
        
            char * s = "foo/baa/hehehe/";
            char * name = NULL;
            char * t = token(s, &name);
            while(t) {
                printf("%s\n", name);
                t = token(NULL, &name);
            }
        
            return 0;
        }
        

        输出:

        foo
        baa
        hehehe
        

        但你基本上是在“重新发明轮子”strtok() 函数..

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-13
          • 2017-01-11
          相关资源
          最近更新 更多