【问题标题】:Confusion regarding pointer size关于指针大小的困惑
【发布时间】:2017-11-29 13:57:05
【问题描述】:

这可能是一个菜鸟问题..但这真的让我很困惑..

下面是一些示例代码

void main() {

    int a = 300;
    char *ptr = &a;
    int *intptr = &a;

    printf("%d\n %d\n", *ptr, *intptr);

}

输出:
44
300

据我了解,为什么取消引用*ptr 打印 44 是因为 char 指针是一个字节,因此它只从 int 的地址读取 8 位...

但是这个question:What is the size of a pointer?声明Regardless of what data type they are pointing to, they have fixed size

我错过了什么。为什么取消引用 char 指针会打印 44,如果指针大小相同?

【问题讨论】:

  • 以上代码中没有任何内容涉及指针大小。
  • 顺便说一句 256 + 44 = 300
  • 指针的大小与它指向的大小不同。这里char *ptr*ptrchar。结果很复杂,因为它确实没有指向char
  • 在您的示例中,您的指针不仅具有相同的大小,而且具有相同的值。但是当取消引用时,其中一个加载一个字节,而另一个加载一个整数。为什么会让人困惑? sizeof(ptr) 是指针大小。 sizeof(*ptr) 是它指向的数据类型的大小。
  • warning: initialization from incompatible pointer type [-Wincompatible-pointer-types] char *ptr = &a;

标签: c windows pointers 64-bit


【解决方案1】:

在您可能遇到的大多数系统上,对象指针(例如指向除函数之外的任何东西的指针)通常大小相同,但不能保证这一点。话虽如此,即使 pointers 的大小可能相同,但它们指向的 types 却不同。

例如,在 64 位 Windows 系统上,指针的大小通常为 8 字节。在您的示例中,您有 char *int * 这很可能都是 8 个字节。这里的区别在于,取消引用 char * 将读/写 1 个字节,而取消引用 int * 将读/写 4 个字节(假设 int 是 32 位)。

假设小端字节顺序,a 在内存中看起来像这样:

  ------------------
a | 44 | 1 | 0 | 0 |
  ------------------

ptrintptr 都包含a 的地址。当取消引用ptr(类型为char *)时,它只查看第一个字节。相反,当取消引用intptrint * 类型)时,它会查看所有 4 个字节。

【讨论】:

  • 谢谢,所以无论其数据类型如何,指针可能具有相同的大小,但是在取消引用时,它取决于数据类型..我正确吗
  • @TheGameiswar 不一定是 8 个字节(不是 8 位)。如果您在 32 位系统上,它可能是 4 个字节。该标准并不能保证所有指针的大小都相同,但实际上它们往往如此。但是,是的,主要区别在于何时取消引用指针。读取/写入的字节数取决于类型。
  • @dbush 很好的答案,但是你对变量 'a' 内存的图片表示对像我这样的菜鸟来说有点混乱。
【解决方案2】:

首先,您的代码不是有效的 C 代码,它还会调用未定义的行为,因此任何事情都可能发生。如果您的编译器没有向您显示任何诊断消息,则需要将其卸载并获得一个可以正常工作的消息。您有以下错误:

  • void main() 仅适用于专门允许这种 main 形式的独立实现。
  • char *ptr = &a; 不是 C 中的有效赋值形式。指针不兼容(1)
  • 使用%d 格式说明符打印字符会调用未定义的行为。

修复这些bug后,我们得到:

#include <stdio.h>

int main (void)
{
  int a = 300;
  char *ptr = (char*)&a;
  int *intptr = &a;

  printf("%d\n %d\n", (int)*ptr, *intptr);
}

这些都不会打印指针的大小。您打印int a 中第一个字节的内容,然后将其转换为char,它可能已签名也可能未签名,并且可能给出不正确的结果(这部分取决于字节序)。然后你打印整个int a的内容。

您似乎想要的是打印指针本身的大小:

printf("%zu %zu\n", sizeof ptr, sizeof intptr);

(1) C11 6.5.16.1 强调我的:

约束
应满足以下条件之一:
/--/
- 左操作数具有原子的、合格的或不合格的指针类型,以及(考虑 左操作数在左值转换后的类型)两个操作数都是 指向兼容类型的合格或不合格版本的指针,以及指向的类型 to by left 具有所有由 right 指向的类型的限定符;

【讨论】:

  • 在 C 中,指向任何对象的指针都可以转换为指向字符类型的指针并返回。
  • @EricPostpischil 不是隐式的,它会违反简单赋值的约束,因为指针不兼容。 C11 6.5.16.1:the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • 使用%d 打印char 值是可以的,因为整数提升。 (除非您使用的是奇怪的 C 实现,其中 intchar 已被扭曲为相同的大小,正如 Stack Overflow 其他地方所讨论的那样。)我认为最好不要用 C 违规来锤击 OP首先,而不是帮助他们了解他们所观察到的。
  • @EricPostpischil 对于普通的 VA 函数,是的,然后应用默认参数提升。但是(功能失调的)printf 系列函数明确将其标记为 UB、C11 7.21.6.1/9 If a conversion specification is invalid, the behavior is undefined. If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.。至于敲击 OP,他们显然是在用他们在一包玉米片中发现的一些随机垃圾来编译他们的代码。我们需要指出,他们需要使用兼容的编译器而不是损坏的编译器。
  • 我认为这是一种过度的解释。例如,6.5.2.2 4 表示“每个参数都分配有相应参数的值”。因此,此处的“相应参数的值”必须是指在本条款中指定的提升和转换之后的参数值,因为在这些之前分配值是不明智的(或总是可能的)。然而,他们并没有写出“在本条款中指定的促销和转换之后相应参数的值”。所以标准可能会使用“arguments”来表示提升和转换后的参数。
【解决方案3】:

在此声明中

printf("%d\n %d\n", *ptr, *intptr);

输出的不是指针本身,而是它们指向的数据。

例如300 可以表示为256 + 44。因此44 可以存储在一个字节中,而 256 可以存储在另一个字节中。而这个表达式*ptr给出了存储在指针ptr指向的字节中的值44。

另一方面,指针intptr 指向int 类型的整个对象,而表达式*intptr 给出的值是300。

如果你想输出存储在你应该写的指针中的地址

printf("%p\n %p\n", ( void * )ptr, ( void * )intptr);

输出你可以写的指针的大小

printf("%zu\n %zu\n", sizeof( ptr ), sizeof( intptr ));

考虑到根据 C 标准,没有参数的函数 main 应声明为

int main( void )

同样在这个声明中你应该使用显式转换

char *ptr = ( char * )&a;

这是一个演示程序

#include <stdio.h>

int main(void) 
{
    int a = 300;
    char *ptr = ( char * )&a;
    int *intptr = &a;

    printf( "*ptr = %d, *intptr = %d\n", *ptr, *intptr );
    printf( "ptr = %p, intptr = %p\n", ( void * )ptr, ( void * )intptr );
    printf( "sizeof( ptr ) = %zu, sizeof( intptr ) = %zu\n", 
        sizeof( ptr ), sizeof( intptr ) );

    return 0;
}

它的输出可能看起来像

*ptr = 44, *intptr = 300
ptr = 0x7ffe5972613c, intptr = 0x7ffe5972613c
sizeof( ptr ) = 8, sizeof( intptr ) = 8

【讨论】:

  • 剩下的就是打印char, int的大小。
【解决方案4】:

如果将指向对象的指针转换为与对象不同的类型,然后通过此指针访问它,则会引入未定义的行为。

语句int a = 300 引入了int 类型的对象,char* ptr = &amp;a 引入了指向char 类型对象的指针,但让它指向int 类型的对象。这本身不是问题,但是通过*ptr 取消引用这个指针是未定义的行为。这与printf 无关——像char x = *ptr 这样的声明也将是UB。但是,像 int x = *((int *)ptr) 这样的语句是可以的,因为 ptr 被转换回它指向的对象的原始类型。

关于指针的大小:指针的大小(即存储指针指向的内存地址所需的值)是固定大小的;它通常在 32 位系统上为 4 个字节,在 64 位系统上为 8 个字节。表示内存地址所需的大小与驻留在指针指向的相应内存地址的对象的大小无关。

以下示例演示了这种行为:

int main() {

    int a = 300;
    void *ptr = &a;
    int *intptr = &a;

    printf("%d %d\n", *((int*)ptr), *intptr);   
}

输出:

300 300

【讨论】:

  • 通过字符指针访问对象是在C中定义的。
  • OP 不会在任何地方投射,而是投射!= 转换。这就是为什么他们的代码char* ptr = &amp;a 不是有效的C。话虽如此,使用指向其他类型的字符指针 调用UB,严格别名规则有一个明确的例外。这个答案基本上有一半是完全不正确的。
  • 这个答案的第一句话是假的。任何指向对象的指针都可以转换为字符类型,并通过字符类型访问对象的字节。正如 Lundin 指出的那样,赋值 char *ptr = &amp;a 并不严格符合 C,但 char *ptr = (char *) &amp;a 解决了这个问题。
  • “如果将指向对象的指针转换为与对象类型不同的类型并通过此指针访问它,则会引入未定义的行为。”对于字符类型来说是完全错误的。您可以从字符指针访问任何内容,但不能相反,即通过另一种指针类型访问一大块字符。请参阅常见问题解答What is the strict aliasing rule?
【解决方案5】:

确实,在little endian架构上,LSB会首先出现在内存中,因此要存储300,内存看起来像

44 , 1 , 0 , 0  (for a size 4 int)

printf("%d\n %d\n", *ptr, *intptr);

不打印指针大小(可能相同),它打印 dereferenced 指针、一个字节的值、一个字符,然后是 int 值。

打印指针大小

printf ("%zu\n%zu\n", sizeof(ptr), sizeof(intptr));

【讨论】:

    【解决方案6】:

    代码

    #include <stdio.h>
    #include <stdint.h>
    
    unsigned int
    trans(unsigned char c){
      if ('0' <=c && c <= '9') return c - '0';
      if ('A' <=c && c <= 'F') return c - 'A' + 0x0A;
      if ('a' <=c && c <= 'f') return c - 'a' + 0x0A;
      return 0;
    }
    
    uint16_t
    hex_to_uint16(const char* s) {
      char *p = (char*) s;
      uint16_t v = 0;
      while (*p) {
        if (p > s) v = v << 4;
        v += trans(*p);
        p++;
      }
      return v;
    }
    
    int main (void)
    {
    
      int n = 1;
        // little endian if true
        if(*(char *)&n == 1) {printf("little\n");}
    
      int a = 300;
      char *ptr =  (char*)&a;
      int *intptr = &a;
    
        printf("charSize:%zu intSize:%zu\n", sizeof (char), sizeof (int));
        printf("PcharSize:%zu PintSize:%zu\n", sizeof (ptr), sizeof (intptr));
    
    
      //printf("Hex: %x, Decimal: %d\n", 0x2c, (int)0x2c ); 
    
      //printf("int: %d\n", hex_to_uint16("2c"));
        //printf("300H: %x\n", (a));
    
    
        printf("PcharAddr: %p\nPintAddr: %p\n", ptr, intptr);
    
    
      printf("int: %d\n", *intptr);
    
    
      printf("LSB  |     |     |  MSB|\n");
      printf("  0  |  1  |  2  |  3  |\n");
      printf("------------------------\n");
      printf("Dec\n");
      printf(" %d     %d     %d     %d\n", *(ptr), *(ptr+1), *(ptr+2), *(ptr+3));
      printf("Hex\n");
      printf(" %x     %x     %x     %x\n", *(ptr), *(ptr+1), *(ptr+2), *(ptr+3));
    
    
    
        printf("To Big Endian:\n%.2x%x%x%x\n", (uint8_t)*(ptr+3), (uint8_t)*(ptr+2), (uint8_t)*(ptr+1), (uint8_t)*(ptr));
    
      ///https://stackoverflow.com/questions/19275955/convert-little-endian-to-big-endian
        /*
         * chux aswer
        uint32_t num = 300;
        uint8_t b[4];
    
        b[0] = (uint8_t) (num >>  0u);
        b[1] = (uint8_t) (num >>  8u);
        b[2] = (uint8_t) (num >> 16u);
        b[3] = (uint8_t) (num >> 24u);
    
      printf("%x%x%x%x\n", b[3], b[2], b[1], b[0]);
    
      */
    
        return 0;
    }
    

    编译、运行

    gcc -Wall -Wextra te.c -o te && ./te
    

    【讨论】:

      【解决方案7】:

      不,不,不

      根据我的理解,为什么取消引用 *ptr 打印 44 是因为 char 指针是一个字节,所以它只从 int 的地址读取 8 位...

      char 是一个字节,而不是 char 指针。如果你想打印指针,你应该

      printf("%p\n %p\n", ptr, intptr);
      

      【讨论】:

      • @babon:然后将它们投射到void *const void *
      • @EricPostpischil 绝对:)
      • 是的,我同意。上面的代码实际上并不是要打印真正的平面地址,而只是表明它是相同的值,无论指针类型;)
      猜你喜欢
      • 2021-07-03
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      相关资源
      最近更新 更多