【问题标题】:How to check for type in C in function如何在函数中检查 C 中的类型
【发布时间】:2010-11-16 20:43:26
【问题描述】:

我正在制作一个函数,它将两个指向字符串的指针作为参数。只要你传递有效的参数,它就可以正常工作。 我想知道如何检查这些指针是否有效,而不是例如两个随机整数。我该怎么做?

char ** LCS ( char * s1, char * s2)    //thats the function

...

LCS(0,0) //...awful crash.. How do I avoid it?

【问题讨论】:

    标签: c string function pointers arguments


    【解决方案1】:

    在函数体中,检查:

    if ((s1==NULL) || (s2==NULL)) {
      /* Do something to indicate bad parameters */
    }
    

    【讨论】:

      【解决方案2】:

      使用文档并遵循 C 的座右铭:“信任程序员”。

      /* s1 and s2 must be both valid pointers to null-terminated strings
      ** otherwise the behaviour is undefined */
      char ** LCS ( char * s1, char * s2);
      

      【讨论】:

      • s/not defined/undefined/,听起来像是引用了 C 标准。
      • 这是 stu**dest 的座右铭 :D 但正如我在下面所说的,让我担心的是自动化测试仪会测试我的功能。也许它会很仁慈,给我的功能只是很好的论据
      • @Randalfien:自动化测试人员是否必须测试strlen?或strcpy?或strtok? ... ...
      【解决方案3】:

      有人用NULL 参数调用你的函数有意义吗?如果没有,您应该在函数的 contract 中禁止 NULL 参数,例如通过在声明上方添加注释,说明它仅适用于有效的非NULL 参数。换句话说,任何使用你的函数的人都同意不提供NULL 参数;那么他们有责任检查这一点,而不是你的。

      如果其中一个或两个参数为NULL 确实有意义,那么您需要决定您的函数在这种情况下的行为方式并因此实现它。在这种情况下,您同意支持NULL 参数并对它们做一些明智的事情,因此有责任检查这一点并采取相应措施(例如if (s1 == NULL))。

      如果您无法为NULL 参数考虑任何合理的行为,请选择第一个选项并完全禁止它们。如果您这样做,那么您的示例调用 LCS(0,0); 违反了合同(即,当函数不同意接受它们时传递 NULL 指针)并且应该被删除。在更复杂的情况下,如果您从变量传递参数并且这些变量有可能指向NULL,那么您必须在调用LCS 之前进行检查,例如if (v1 && v2) { LCS(v1,v2); } else { … }.

      跟踪与此相关的可能错误,您可以使用assert 进行检查,例如:

      #include <assert.h>
      
      char **LCS (char *s1, char *s2) {
          assert(s1);
          assert(s2);
          …
      }
      

      如果s1s2NULL,这将导致您的程序退出,除非在包含assert.h 之前定义了NDEBUG(在这种情况下,断言什么都不做) .所以断言是一种在开发过程中检查调用者没有给你NULL参数的方法,但如果他们给了它仍然是一个错误。

      至于其他无效指针,您甚至无法真正可靠地检查,例如没有办法知道调用者是否有一个非常奇怪的字符串,或者他们是否只是传递了错误的地址。这也是他们避免的责任,LCS 应该简单地假设调用者正在给你有效的数据。当然,如果您有其他限制,例如参数字符串的最大长度,那么你必须让调用者清楚这些限制(即指定函数的合同,“这个函数执行 X [你作为 LCS 的实现者的责任],前提是...... [他们作为LCS]”)。这适用于 all 编程,例如 C 标准规定了必须如何使用语言本身和标准库函数(例如,不能被零除,strcpy 的参数字符串不能重叠等)。

      【讨论】:

        【解决方案4】:

        在 C 语言中,恐怕你必须小心并希望程序员知道该做什么。

        在这种情况下,0(零、空、NULL)是函数的有效输入。

        通常情况下,您至少可以通过检查输入是否有效来保护函数。

        例如...

        char** LCS (char *s1, char *s2 )
        {
          if ( s1 == 0 )
            return ...;
          if ( s2 == 0 )
            return ...;
        
          if ( strlen( s1 ) == 0 )
             return ...
        
        /// do something ...
        }
        

        【讨论】:

        • 那太臭了。特别是因为我的程序将由自动测试仪进行测试,它会向它抛出任何东西,而崩溃意味着失败。
        • 使用 C 编程时,您无法保证任何安全性,即使是语言本身也无法保证。我可以将非常邪恶的指针传递给您的函数,以便检查它们是否有效将比实际计算花费更多。在 C 中,你真的必须信任调用者。
        • 不是 (strlen(s1) == 0) 和 (s1 == 0) 一样吗?
        • @Kevin:没有。 char *s1 = NULL; assert(s1 == 0); s1 = ""; assert(strlen(s1) == 0);。当s1 == 0 你不能做strlen(s1)。当strlen(s1) == 0 时,s1 != 0 是绝对确定的。
        • 你可以用“*s1 == '\0'”代替“strlen(s1) == 0”。 strlen 只计算字符到 '\0'。
        【解决方案5】:

        您可以做的最好的事情是检查 NULL (0)。否则,没有 标准 方法来判断非 NULL 指针值是否有效。可能有一些特定于平台的 hack 可用,但一般来说,这个问题是通过文档和良好的内存管理卫生来处理的。

        【讨论】:

          【解决方案6】:

          您可以使用这样的结构来实现自己的类型检查。但是您也可以只使用具有适当类型检查的语言。 :)

          typedef struct Var {
              enum Type { int, ptr, float ... } type;
          
              union {
                  int Int;
                  void *Ptr;
                  float Float;
                  ...
              } data;
          } Var; 
          

          【讨论】:

            【解决方案7】:

            C 的意识形态围绕“程序员知道他在做什么”这一原则展开。 C 如此轻量和快速的一半原因是因为它不执行此类类型检查。

            如果您确实需要执行此类检查,最好使用 C++,使用引用(确保为非空)而不是指针。

            【讨论】:

              【解决方案8】:

              首先,正如其他人所说:

              • 检查 NULL 参数

              其他的都是启发式和仔细的编程

              您可以向调用者提供函数原型并将警告设置为 11:(至少 -Werror -Wall-Wextra 用于 gcc)。如果传入了不正确类型的参数,这将导致编译错误。如果调用者首先将其参数强制转换为 char *s(例如 LCS((char*)1, (char*)1 )

              您可以在参数上调用 strlen,但如果值是非 NULL 但仍然是非法值,则 strlen 可能会崩溃。

              您可以尝试查看指针是否在程序的有效段中。这不是便携的,也不是万无一失的。

              所以,总而言之,检查 NULL,并将警告设置为 11。这就是实践中所做的。

              【讨论】:

                【解决方案9】:

                你真的不能这样做。首先,如果程序员将任意整数转换为指针,那么它们实际上可能是地址空间内的有效指针——它们甚至可能指向以空字符结尾的字符数组(事实上,如果它们在你的地址空间内,它们将指向,因为那里的数据将被视为字符,并且在某些时候会有一个 0 字节)。

                您可以测试几个无效的(对于应用程序,无论如何)指针值,包括 NULL 甚至可能指向进程地址空间第一页的任何值(它通常没有映射,可以安全地假设不映射)有效)。在某些系统上,还有其他页面从未被映射(如最后一页)。一些系统也有方法询问进程的内存映射(Linux 下的 /proc/self/maps),您可以(有很多麻烦)查看指针是否在具有适当访问权限的映射区域内。

                如果您使用 *nix 系统,您可以为SIGSEGV 注册一个信号处理程序,当您的程序尝试访问它不应该访问的内存时,该处理程序会被引发。然后你可以抓住它,并通过一些工作弄清楚发生了什么。您可以做的另一件事是调用一个接受指针的系统调用,并使用您作为参数传递的指针并查看它是否失败(使用errno == EFAULT)。这可能不好,因为系统调用除了测试内存的读取和/或写入权限之外还做其他事情。您始终可以写入指向 /dev/null 或 /dev/zero 的指针指向的第一个字节(使用 write 系统调用,而不是 stdio 函数)以确定您是否具有读取权限,但是如果您从 /dev 读取一个字节/zero 或 /dev/random 到指向的第一个字节(使用读取系统调用,而不是 stdio 函数),但如果该区域的数据很重要,那么您将覆盖它的一个字节。如果您试图将该数据的副本保存到局部变量中以便在测试后恢复它,那么当您在程序中读取它时可能会导致错误。不过,您可以详细说明并将其写出,然后再读回以测试两种访问权限,但这会变得复杂。

                最好的办法是只依靠函数的用户来做正确的事情。

                【讨论】:

                  猜你喜欢
                  • 2013-07-10
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-03-08
                  • 2017-05-15
                  • 1970-01-01
                  • 2017-12-04
                  • 2012-02-08
                  • 1970-01-01
                  相关资源
                  最近更新 更多