【问题标题】:What are the errors in this function from a Microsoft interview question?Microsoft 面试问题中此功能的错误是什么?
【发布时间】:2010-09-21 09:09:43
【问题描述】:

我在 MS 书面步行采访中被问到这个问题:

在下面的程序中查找错误,该程序应该返回一个带有\n的新字符串。

char* AddnewlinetoString(char *s)
{
  char buffer[1024];
  strcpy(buffer,s);
  buffer[strlen(s)-1] = '\n';
  return buffer;
}

我尝试自己编写代码,并通过将缓冲区变量设为全局变量并使用buffer[strlen(s)] = '\n' 使其工作。但是不知道里面还有很多其他的bug。

【问题讨论】:

  • 那么你看到了哪些?
  • 该代码来自 windows 95 吗?
  • 让我们看看Java学校的人回答这个:)
  • 为什么这个问题已经结束了?它与编程非常相关。
  • 有两种程序员——一种会仔细思考问题以查看所有相关细节,另一种会不断尝试直到看起来可行。猜猜你属于哪一组?猜猜微软在寻找哪种类型的产品?

标签: c string


【解决方案1】:

我可以看到一些:

未检查输入字符串的长度。

如果strlen(s) > 1023 怎么办?缓冲区中最多可以容纳一个长度为1023 的字符串。

\n覆盖最后一个字符

您正在用换行符覆盖最后一个字符。你的\n 应该去以前\0 的位置,你需要在\n 之后添加一个新的\0

变量缓冲区对于函数来说是本地的,你正在返回它的地址。

缓冲区的内存在堆栈上分配,一旦函数返回,该内存就会被释放。

我愿意:

char* AddnewlinetoString(char *s) {

  size_t buffLen = strlen(s) + 2; // +1 for '\n' +1 for '\0'
  char *buffer = malloc(buffLen); 
  if(!buffer) {
   fprintf(stderr,"Error allocting\n");
   exit(1);
  }
  strcpy(buffer,s);
  buffer[buffLen-2] = '\n';
  buffer[buffLen-1] = 0;
  return buffer;
}

【讨论】:

  • 有趣的是,\0 可能仍然存在(如果第一个错误没有触发它)。再看一遍,\0 被覆盖了吗?
  • 2. strlen 在长度中不包含终止符。所以最多,代码只是用换行符替换字符串的最后一个字符,但如果 nul 已经存在,它将保留。
  • @TygerKrash:堆栈保存当前函数的本地值。一旦函数返回,你的指针指向垃圾,即你不知道那里有什么(例如下一个函数的局部变量可以覆盖你的指针指向的区域)。
  • 这样使用会泄漏内存:str = AddNewlineToString(str);
  • @sje397:这不是函数的问题,只要正确记录调用者拥有返回的缓冲区,并负责适当地释放它。唯一的选择是使用全局缓冲区,它有更严重的问题。 *编辑:当然,鉴于您被类似 C 的界面所困。
【解决方案2】:
  1. strcpy 没有限制,最好使用 strncpy。
  2. 您正在复制到静态缓冲区并返回指针。

【讨论】:

  • 确定不是静态缓冲区?
  • 应该确保缓冲区大小合适,而不是strncpy。如果您截断输入而不是附加换行符,这到底有什么好处?这应该解决这两个问题。
  • 如果有的话,strlcpy 会更好。
【解决方案3】:

这是一个没有错误的 C++ 版本:

std::string AddnewlinetoString(std::string const& s)
{
    return s + "\n";
}

这就是我可能会用 C++0x 编写的方式:

std::string AddnewlinetoString(std::string s)
{
    return std::move(s += "\n");
}

【讨论】:

  • 虽然没有回答问题:)
  • 没错,但是...我不认为这些练习的目的是想出一个更好的版本本身,而是要了解错误的复杂性.将其视为调试平庸同事工作的测试,而不是自己编写新代码的测试。
  • 问题被标记为 C++,这意味着这是一个潜在的正确答案!
  • @Steve:我添加了您对 const 版本的建议参考,但我仍然更喜欢 pass by value 版本,尤其是在 std::move 中编辑的 :)
  • 总之,我认为最好的方法是,对于 C++03 和 C++0x,std::string AddnewlinetoString(std::string s){ s += '\n'; return s; } 考虑参数:在 C++03 中,使用左值时无论如何,您都会制作副本,因此不会丢失任何内容。并且编译器将省略右值的副本。在 C++0x 中,您再次制作所需的副本,但保证在给定右值时移动构造参数。所以那里没有丢失任何东西。然后你做你的操作,然后返回副本。在 C++03 中,您获得 NRVO,而在 C++0x 中,您获得隐式移动返回。 (再次,我认为。)
【解决方案4】:

我还要补充一点,方法的名称应该坚持模式,每个单词都应该以大写字母开头:

char* AddNewlineToString(char *s)
{
}

ps。谢谢 Konrad,我已经按照您的建议更改了方法名称

【讨论】:

  • 但是“换行符”(如“换行符”,而不是“换行符”)是一个字。名称应为AddNewlineToString
【解决方案5】:

三件事

   int len = strlen(s);
   char* buffer = (char*) malloc (len + 2);   // 1
   strcpy(buffer,s);
   buffer[len] = '\n';           // 2 
   buffer[len+1] = '\0';         // 3
   return buffer;

编辑:基于 cmets

【讨论】:

  • 调用strlen() 三次而不是使用临时可能会导致令人失望的结果。
  • @aeh:strlen 不计算终止的 '\0',因此实际上它替换了 '\0' 之前的最后一个字符,并且对于仅包含终止符的空字符串失败。
  • @aeh:您的答案包含一个错误。您只需返回一个新字符串,将 s 的最后一个字符替换为\n。 len 太小了一个字符。
  • 您不会检查 s 是否为 NULLmalloc() 是否失败。
  • 他们也想char* AddnewlinetoString(const char *s)
【解决方案6】:

这是一个更正的版本(社区 wiki,以防我遗漏了什么)

// caller must free() returned buffer string!
char* AddnewlinetoString(char *s)
{
  size_t len;
  char * buffer;

  if (s == NULL)
    s = "";

  len = strlen(s);
  buffer = malloc(len+2);
  if (buffer == NULL)
    abort();
  strcpy(buffer,s);
  buffer[len] = '\n';
  buffer[len+1] = 0;
  return buffer;
}

正如 tony 所提到的,s 可能是一个有效的地址,但仍然是一个格式错误的 c 字符串,没有空字节。该函数最终可能会读取,直到它导致段错误或其他一些可怕的事情。虽然这仍然是惯用的 C,但大多数人更喜欢计数字符串(而不是以 null 结尾的字符串。)

// caller must free() returned buffer string!
char* AddnewlinetoStringN(char *s, size_t len)
{
  char * buffer;

  if (s == NULL)
    s = "";

  buffer = malloc(len+1); // only add 1 byte, since there's no need for the nul
  if (buffer == NULL)
    abort();
  strncpy(buffer,s,len);
  buffer[len] = '\n';
  return buffer;
}

【讨论】:

  • 我认为你的 malloc 少了一个字符 - 你需要为 \n 和终止 NULL 留出空间,所以 len+2。
  • 我首先会亲自检查 s 是否为空。这是字符串 args 的常见问题,如果您不检查它,它将断言。
  • @Tony:不错的电话,你觉得这个版本怎么样?
  • 还有一个小洞......如果你传入一个没有终止零的字符串,但要确定这一点,你需要假设字符串的最大长度。
  • 这是你现在为微软工作需要通过的问题级别吗?要为 Google 工作,他们会问你如何移山。
【解决方案7】:

这段代码的主要问题是它容易受到stack buffer overflow 的攻击。这是一个经典的例子。

基本上,输入的 char* 可以超过 1024 字节;这些额外的字节将覆盖堆栈,允许攻击者修改函数返回指针以指向他们的恶意代码。然后你的程序会在不知不觉中执行恶意代码。

微软可能会非常关心这些类型的攻击,因为 Code Red Worm 曾在 2001 年使用堆栈缓冲区溢出攻击数十万台运行 IIS Web 服务器软件的计算机。

【讨论】:

    【解决方案8】:

    不需要返回指针。更改传入指针。

    int len = strlen(s); s[len] = '\n'; s[len + 1] = '\0';

    【讨论】:

    • 它不起作用,第一个赋值替换终止零,第二个strlen将返回无效结果。你应该交换它们。
    • 这不一定总是有效。如果输入是一个string literal 或一个已经填充了len-1 字符的char 数组,这样当您访问索引len+1 时,您会跳过分配的内存。
    • @codaddict:如果它是字符串文字,则有人忽略了签名 - 它不是 const 参数。
    【解决方案9】:

    在 C++ 中应该是

    std::string AddNewlineToString(const std::string& s) // pass by const reference
    {
        return s + '\n'; // and let optimizer optimize memory allocations
    }
    

    【讨论】:

      【解决方案10】:

      使用 strdup 可以非常简单:

      char* AddnewlinetoString(char *s) {
      char *buffer = strdup(s);
      buffer[strlen(s)-1] = '\n';
      return buffer;
      }

      【讨论】:

        【解决方案11】:

        对于 C 风格的字符串,它可能是

        char* // we want return a mutable string? OK
        AddNewlineToString(
          const char* s // We don't need to change the original string, so it's const.
        )
        {
             const size_t MAX_SIZE = 1024; // if it's a mutable string,
                                           // it should have a known capacity.
        
             size_t len = strlen(s);
             if(len + sizeof("\n") // To avoid the magic number "2".
                 > MAX_SIZE)
                 return NULL; // We don't know what to do with this situation,
                              // the user will check the result and make a decision -
                              // to log, raise exception, exit(), etc.
        
             // static                    // We want a thread-safe result
             char* buf = new char[1024];  // so we allocate memory in the heap
                                          // and it's C-language-string but not C language :)
        
             memcpy(buf, s, len); // Copy terminating zero, and rewrite it later? NO.
             memcpy(buf + len, "\n", sizeof("\n")); // The compiler will do it in one action like
                // *(int16_t*)(buf + len) = *(int16_t*)"\n";
                // rather than two assignments.
        
             return buf;
        }
        

        【讨论】:

        • 您对sizeof("\n") 的使用最初并不清楚;在这种情况下,我不确定避免使用“幻数”是否值得。
        • @Gman:例如,如果我们想提供一个与 C 兼容的接口。对于堆分配的内存,我们应该只提供一个函数来删除任何堆分配的内存,但是对于按字符串分配的内存,我们不能轻易做到。
        • @Abyx:用“它可能在 C 中使用”来证明某事的合理性很奇怪。我不会在我的任何代码中假设,这样的假设肯定超出了问题的范围。
        • @GMan:不是“在 C 中”,而是“在 C 中兼容”。它的意思是“在 C#、F#、Python (ctypes)、Haskell 和许多其他语言中”。您不能从 DLL 导出带有 std::string 结果的函数,并在用另一种语言编写的模块(或由另一个 C++ 编译器编译的模块)中使用此函数。这是微软的问题,是关于 Windows 的,而不是仅使用 gcc 进行跨平台编程。
        • @Abyx:公平,但我重申我的观点:在 C++ 中,编写 C++。如果您碰巧需要与另一种语言交互然后更改代码以执行此操作。我认为用“它可以被另一种语言使用”来证明你的答案是糟糕的资源管理是很糟糕的,因为没有任何关于这种要求的说明。
        【解决方案12】:

        这里有一个更简单的方法:

        char* AddnewlinetoString(char *s)
        {
          char buffer[strlen(s)+1];
          snprintf(buffer,strlen(s),"%s\n",s);
          return buffer;
        }
        

        【讨论】:

          猜你喜欢
          • 2017-05-04
          • 1970-01-01
          • 2019-11-27
          • 1970-01-01
          • 1970-01-01
          • 2019-03-17
          • 1970-01-01
          • 2018-12-19
          • 1970-01-01
          相关资源
          最近更新 更多