【问题标题】:Pointer to specific address in C指向C中特定地址的指针
【发布时间】:2016-05-19 23:37:33
【问题描述】:

假设地址0xCF800000可以写:

A) 说两个代码产生相同的结果是否正确?

int main( void )
{
  volatile unsigned long *pt = (volatile unsigned long *) 0xCF800000;
  *pt = 0x00000000;
}

int main( void )
{
  (*(volatile unsigned long *) 0xCF800000) = 0x00000000;
}

B) 在第一个代码中,0xCF800000 之前的语句“(volatile unsigned long *)”是必要的还是冗余的?

C) 在第一个代码中有一个变量 pt,它有自己的地址,我在其中放置了一些内容:0xCF800000。通过取消对 pt 的引用,计算机将获取 pt (0xCF800000) 的内容,“定位”该地址,并将 0x00000000 分配给该位置。在第二个代码上,我无法准确理解它是如何工作的,因为没有变量。看起来信息 0xCF800000 是“无处”。

【问题讨论】:

  • 注意:我认为不需要volatile。给定地址的对象只被引用一次,所以无论如何都必须检索它。
  • 这些问题来自求职面试吗?
  • 啊哈哈哈。没有为什么? @wildplasser 确实! volatile 的存在只是因为在关于 GPIO 的课程中​​出现了这种疑问
  • 顺便说一句:你不需要*pt = 0x00000000; 因为*pt = 0; 也会这样做。闻起来像货物崇拜...额外:常量0xCF800000 可能在没有演员或u 后缀的情况下签名。
  • A) 是的。 B) 必要的。 C)您需要查看程序集以了解编译器如何实现您的代码。否则,您只需要信任互联网上的随机陌生人。

标签: c pointers memory-address


【解决方案1】:

您进行了强制转换为 (unsigned long *),这意味着编译器将此值视为指向 unsigned long 的指针并且不要抱怨。 但正如@user3386109 所说,检查自己总是好的。

【讨论】:

    【解决方案2】:

    您实际上发布了 3 个问题,每个问题都需要单独的答案,所以:
    关于答:
    取决于您写入此内存位置的值(原文如此!)。

    只要这是零或许多小值,您就安全了。 但是如果这个值的最高位是1并且如果由于某些原因它会被符号扩展(取决于你的演员,请参阅我的回答回答你的问题 B)——那么你就会遇到问题。
    为避免这些问题,您应该在该值后面加上 u 以表明它是无符号的(同样,如果它为零,这并不重要)。

    因此,如果您要存储的常量值是例如0xA0000000(注意最重要的位是1!)你应该把它写成:0xA0000000u。

    顺便说一句:0x00000000 只是 0,你不需要写那么多零它,但我知道这是更长代码的一部分,您希望与其他更长的值保持一致

    【讨论】:

      【解决方案3】:

      在第二个代码中,我无法准确理解它是如何工作的,因为没有变量。看起来信息 0xCF800000 是“无处”。

      解决您的问题 C):它是一个数字文字,稍后您将其转换为指针。在大多数计算机系统中,只要 (0xCF800000) 的文字将被编译为立即数,而取消引用指针值是一条内存读取指令。

      让我们看看你的第二个示例代码在 x86 上编译成什么:

          .section    __TEXT,__text,regular,pure_instructions
          .macosx_version_min 10, 11
          .globl  _main
          .align  4, 0x90
      _main:                                  ## @main
          .cfi_startproc
      ## BB#0:
          pushq   %rbp
      Ltmp0:
          .cfi_def_cfa_offset 16
      Ltmp1:
          .cfi_offset %rbp, -16
          movq    %rsp, %rbp
      Ltmp2:
          .cfi_def_cfa_register %rbp
          xorl    %eax, %eax
          movl    $3481272320, %ecx       ## imm = 0xCF800000
          movl    %ecx, %edx
          movq    $0, (%rdx)
          popq    %rbp
          retq
          .cfi_endproc
      

      特别是三行

          movl    $3481272320, %ecx       ## imm = 0xCF800000
          movl    %ecx, %edx
          movq    $0, (%rdx)
      

      将一个常数值读入寄存器,然后将0写入与该寄存器值地址相同的内存中。

      【讨论】:

        【解决方案4】:

        您实际上发布了 3 个问题,每个问题都需要单独的答案,所以:
        关于 C:
        你问如何: (*(volatile unsigned long *) 0xCF800000) = 0x00000000; 有效,但我相信你知道这一点,你只是不知道你碰巧知道。 ;-)
        上面的指令只是一个作业。要使作业生效,必须先评估其左侧和右侧。
        右侧的有效表达式种类繁多。左侧有很多限制,因为它必须评估内存位置。这种内存位置的一个例子是int a,所以你可以写:a = 0x00000000
        另一个内存位置是int tab[N],所以你可以写:tab[0]= 0x00000000;
        另一个例子是int* p,所以你可以写:*p = 0x00000000
        如您所见,我们非常接近您的问题:如果您同意扩展为内存地址的 *p 可以在赋值指令的左侧使用,为什么不应该使用常量?
        常量在这里完全没问题,所以你甚至可以写得很简单:
        *(int*)0 = 5
        这意味着:取值 0,然后将其转换为(=像对待它一样)带有 int* 的地址,然后使用一元 * 运算符取消引用此地址,然后将整数值 5 分配给此内存位置。当然,我的最后一个示例假设内存位置 0 是可写的。此外,我在这个线程中的所有两个关于位扩展的答案都应该在这里应用,我现在只是为了清楚起见而跳过强制转换

        【讨论】:

          【解决方案5】:

          您实际上发布了 3 个问题,每个问题都需要单独的答案,所以:
          关于 B:
          代码:(volatile unsigned long *) 是一个演员表。它的必要性取决于用于在编译器中存储地址的整数类型的大小(这种类型应该由您的编译器将typedef'ed 到size_t)。 简而言之:
          这里没有强制转换也可以,只要这个字面常量的位数与编译器使用的地址一样多,即如果您的编译器使用 32 位地址。
          如果您的编译器使用超过 32 位的地址,则需要强制转换。这发挥了它的作用,因为您将这个文字常量取消引用为地址(使用一元 * 运算符)。在这种情况下,请记住像 0xCF800000 这样的字面常量将从其当前的 32 位扩展。因为0xCF800000 的最高位是“1”,所以这将导致所谓的签名扩展(前导“1”将被复制到最高有效位),所以你会得到一个与您想要的内存地址不同!

          另一个问题是,您的演员阵容中是否需要 volatile。在某些特定情况下,您只需要volatile,即当内存位置可以被一些其他代码/设备同时使用时。见Why is volatile needed in C? .

          【讨论】:

            猜你喜欢
            • 2021-09-20
            • 2011-01-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-24
            • 1970-01-01
            • 1970-01-01
            • 2018-12-23
            相关资源
            最近更新 更多