【问题标题】:(*(char **)(void *)&"a")[0] created memory error - 指针问题
【发布时间】:2022-01-23 10:43:47
【问题描述】:

我一直在尝试编写一个函数,用于对通用二进制搜索函数进行字符串比较。 但是,在编写函数时,我意识到我的指针取消引用不起作用。

本质上,这是行不通的:

printf("***a[0] = %c\n", (*(char **)(void *)&"a")[0]);

我运行了调试器,它告诉我 EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

但是,这个极其相似的代码(我相信它与我之前的代码相同)确实有效。

char * stringa = "a";
printf("***stringa[0] = %c\n", (*(char **)(void *)&stringa)[0]);

我不明白为什么第二个有效,但第一个无效。我的理解是“a”和stringa都代表一个字符数组开头的内存地址。

提前谢谢你。

【问题讨论】:

  • 数组和指针不一样。在某些情况下,数组会衰减为指针,但这不是其中之一。
  • 为什么不((char*)(void *)&"hello")[0],或者简单地说,((char*)&"hello")[0]?
  • 我正在尝试编写一个通用的二进制搜索,所以我的比较两个字符串的函数应该将两个数组的指针作为 void*s
  • int StrCmp2(void * vp1, void * vp2) { printf("输入的 StrCmp2");整数差= 0; char * str1 = * (char **) vp1; char * str2 = * (char **) vp2;诠释 i1 = 0;诠释 i2 = 0; printf("!!!str1[0] = %c", str1[0]); char char1 = str1[i1]; char char2 = str2[i2];这就是我的代码开始的方式
  • 这段代码在 char1 = str1[i1];我已经用调试器检查过了。

标签: arrays c pointers types dereference


【解决方案1】:

首先,检查这条规则 - 来自 C11 Standard#6.3.2.1p3 [强调添加]:

3 除了是sizeof运算符、_Alignof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串字面量, 类型为“类型数组”的表达式转换为类型为“类型指针”类型的表达式,该类型指向数组对象的初始元素,并且不是左值。如果数组对象有注册存储类,则行为未定义。

来自String literals[强调添加]:

就地构造一个指定字符数组类型的未命名对象,在需要将字符串嵌入源代码时使用。

让我们先解码:

char * stringa = "a";
printf("***stringa[0] = %c\n", (*(char **)(void *)&stringa)[0]);

在此语句char * stringa = "a"; 中,字符串"a" 将转换为指向类型char 的指针,该类型指向字符串"a" 的初始元素。因此,在初始化之后,stringa 将指向字符串文字 "a" 的第一个元素。

&stringa 的类型为 char **。取消引用它将给出 char * 类型,它只是字符串 "a" 并将 [0] 应用于它会给出字符 'a'。

现在,让我们解码:

printf("***a[0] = %c\n", (*(char **)(void *)&"a")[0]);

因为,这里你给出一元运算符&,所以在这个表达式中,(*(char **)(void *)&"a")[0],字符串"a"将不转换为指向其初始元素的指针,&"a" 将给出const char (*)[2] 类型的指针,该指针将被强制转换为 char ** 类型。

取消引用此指针将在地址处给出值,该地址只不过是字符串"a",它会将其视为char 类型的指针(因为类型转换char **)并将[0] 应用于它。这意味着,它正在尝试执行类似 ((char *)0x0000000000000061)[0](0x61 是字符 'a' 的十六进制值)的操作,这会导致错误 EXC_BAD_ACCESS。

相反,你应该这样做

printf("***a[0] = %c\n", (*(const char (*)[2])(void *)&"a")[0]);

编辑:

OP 仍然很困惑。此编辑尝试以不同的方式解释表达式(在帖子中)。

来自 cmets:
操作:But you wrote ((const char ()[2])(void )&"a")[0] works! There are two dereferencing operations ( and [0]) going on here!

不确定您是否知道,但我认为,分享来自 C11 Standard#6.5.2.1p2 的 [] 运算符的定义很好:

下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))。

表达式(*(char **)(void *)&stringa)[0]:

     (*(char **)(void *)&stringa)[0]
      | |                      |
      | +----------------------+
      |             |
      |       this will result in 
      |       type casting a pointer 
      |       of type char ** to char **
      |
      |
   This dereferencing 
   will be applied on result of &stringa
   i.e. ( * ( &stringa ) )
   and result in stringa
   i.e. this
         |
         |
         |   &stringa  (its type is char **)
         |      +-------+
         |      |  800  |---+
         |      +-------+   |
         |                  |
         +-------> stringa  |
                /       +-------+  (pointer stringa pointing to first char of string "a"
               /        |  200  |---+     (type of stringa is char *)
              |         +-------+   |
       now apply [0]    800         |
       to it                        |
       i.e. stringa[0].            +-------+
       stringa[0] is           +-> | a | 0 |   (string literal - "a")
       equivalent to           |   +-------+
       *((stringa) + (0))      |   200        ---> address of "a"
       i.e.                    |
       *(200 + 0),             |            
       add 0 to address 200    |
       and dereference it.     |
       *(200 + 0) => *(200)    |
       dereferencing address   |
       200 will result in      |
       value at that address   |
       which is character      |
       'a', that means,        |
       *(200) result in -------+

表达式(*(char **)(void *)&"a")[0]:

 (*(char **)(void *)&"a")[0]
  | |                  |
  | +------------------+
  |          |
  |       this will result in 
  |       type casting a pointer 
  |       of type const char (*)[2] to char **
  |       
  |
 this dereferencing will be
 applied to pointer of type 
 char ** which is actually a
 pointer of type char (*)[2]
 i.e. *(&"a").
 It will result in value at address 200
 which is nothing but string "a"
 but since we are type casting 
 &"a" with double pointer (char **)
 so single dereference result 
 will be considered as pointer of 
 type char i.e. char *.
    *(char **)(void *)&"a"
          |
          |
          |            &"a" (its type is const char (*)[2] because type of "a" is
          |             +-------+      const char [2] i.e. array of 2 characters)
          |             |  200  |---+
          |             +-------+   |
          |                         |
          |                         |
          |                         |
          |                       +-------+
          +------------------>    | a | 0 |   (string literal - "a")
                            /     +-------+
                           /      200        ---> address of "a"
                          |
                          |
                The content at this location will be 
                treated as pointer (of type char *)
                i.e. the hex of "a" (0x0061) [because the string has character `a` followed by null character]
                will be treated as pointer.
                Applying [0] to this pointer
                i.e. (0x0061)[0], which is 
                equivalent to (* ((0x0061) + 0)).
                (* ((0x0061) + 0)) => *(0x0061)
                i.e. trying to dereference 0x0061
                Hence, resulting in bad access error.

表达式(*(const char (*)[2])(void *)&"a")[0]:

 (*(const char (*)[2])(void *)&"a")[0]
  | |                            |
  | +----------------------------+
  |          |
  |       this will result in
  |       type casting a pointer
  |       of type const char (*)[2] to const char (*)[2]
  |
  |
 this dereferencing will be
 applied to pointer of type
 const char (*)[2]
 i.e. *(&"a")
 and result string "a"
 whose type is const char [2]
          |
          |
          |            &"a" (its type is const char (*)[2] because type of "a" is
          |             +-------+      const char [2] i.e. array of 2 characters)
          |             |  200  |---+
          |             +-------+   |
          |                         |
          |                         |
          |                         |
          |                       +-------+
          +------------------>    | a | 0 |   (string literal - "a")
                            /     +-------+
                           /      200        ---> address of "a"
                          |
                          |
                Apply [0] to "a"
                i.e. "a"[0].
                Now, scroll to the top of my post
                and check string literal definition -

                string literal constructs unnamed object of character array type.....

                also, read rule 6.3.2.1p3 
                (which is applicable for an array of type) -

                ....an expression that has type 'array of type' is converted   
                to an expression with type 'pointer to type' that points to   
                the initial element of the array object. ....
                
                So, "a" (in expression "a"[0]) will be converted to pointer 
                to initial element i.e. pointer to character `a` which is
                nothing but address 200.
                "a"[0]  ->  (* ((a) + (0))) -> (* ((200) + (0)))
                -> (* (200)) -> 'a'

来自 cmets:
操作:there is no such thing as an object in C ....

不要将单词 object 与 C++ 或其他面向对象语言中的对象混淆。
这就是 C 标准定义对象的方式:

来自 C11 标准#3.15p1

1 个对象
执行环境中的数据存储区域,其内容可以表示值

例如- int x; --> x 是 int 类型的对象。

如果您还有其他问题,请告诉我。

【讨论】:

  • 你能解释一下 const char (*)[2] 是什么吗?如果这不是指向指针的指针,那么为什么解引用然后执行 [0](这是一种隐式指针算术和解引用)会导致数组中的第一个字符?
  • @jho317 字符串文字"a" 将构造一个未命名的字符数组类型对象,类似于(仅用于说明)char arr[2] = {'a', '\0'}。 arr 的类型是 char [2],&arr 的类型是 char (*)[2]。因此,&a 的类型是 const char (*)[2](const 修饰符,因为它的字符串字面量)。 “.....那么为什么解引用然后做[0]会导致数组中的第一个字符?”,只有解引用会导致数组的第一个字符而不是“解引用然后做[0]”。请再次阅读我的答案的后半部分。
  • 但是你写的 ((const char ()[2])(void )&"a")[0] 有效!这里有两个取消引用操作( 和 [0])!另外,C不是纯粹的命令吗? C 中没有对象之类的东西,所以“构造一个未命名的字符数组类型的对象”对我来说非常混乱。
  • @jho317 我已经编辑了我的帖子并回答了您的问题。请检查。
【解决方案2】:

指针不是数组。数组不是指针。

  • &stringa 产生一个指向 char** 类型指针的指针。
  • &"a" 产生char(*)[2] 类型的数组指针。它与char** 不兼容。

您尝试通过将char(*)[2] 视为无效的char** 来取消引用char(*)[2] - 它们不是兼容的类型,实际上实际的数组指针说“在地址x 有数据”但是转换时说“在地址 x 处有一个指针”。

如果您尝试打印printf("%p\n", *(char **)(void *)&"a");,您得到的不是地址而是数据。我得到类似<garbage> 0061 的东西,这是一个试图将字符串转换为更大整数的小端机器。在内存中,您将有 0x61 ('a') 然后 0x00(空项) - 字符串本身,而不是您可以取消引用的地址。

【讨论】:

  • &"a" 和 "a" 的工作方式相同,这很奇怪。
  • @jho317 他们没有。 "a" 为您提供在大多数表达式中使用时会衰减为指向其第一个元素的指针 (char*) 的数组。但是,& 表达式中不会发生这种衰减,因此您会得到一个数组指针 (char(*)[2])。
猜你喜欢
  • 1970-01-01
  • 2016-01-20
  • 1970-01-01
  • 2021-02-28
  • 1970-01-01
  • 1970-01-01
  • 2012-02-03
  • 2019-05-24
  • 2013-08-24
相关资源
最近更新 更多