【问题标题】:Why does following C code print 45 in case of int 45 and 36 in case of STRING and ASCII value of CHAR?为什么以下 C 代码在 int 45 的情况下打印 45,在 STRING 和 CHAR 的 ASCII 值的情况下打印 36?
【发布时间】:2014-07-16 07:27:09
【问题描述】:
struct s{
int a;
char c;
};
int main()
{
    struct s b = {5,'a'};
    char *p =(char *)&b;
    *p = 45;
    printf("%d " , b.a);
    return 0;
}

如果 *p 更改为任何 character ,则打印 ASCII 的字符值,如果 *p 更改为任何 string ("xyz") ,则打印 36 。为什么会这样?

你能给出结构 s 和 *p 的内存映射吗?

据我所知,结构体

s as z-> (****)(*) assuming 4 byte for int . 并且当 s 初始化时,它会变成 (0000000 00000000 00000000 00000101)(ASCII OF char a) 并且 *p 指向 z 的起始地址。并且 *p 是一个字符指针,因此它将在每个 BYTE 位置存储 ASCII 值,即每个 * 将被 char 的 ASCII 占用。但现在我们将其设为 45,因此 z 将变为 (45 0 0 5 )(ASCII of char a) 。但这不是真的,为什么?

【问题讨论】:

  • @πάνταῥεῖ 更具体..
  • 顺便说一句:这也取决于您使用的是小端还是大端。
  • 问题的重点是为什么当您使用 *p = 45 写入 char 时似乎会写入整个 int 吗?
  • string("foo") 肯定不是 C。
  • @Jarod42 它只适用于小端,在大端输出将是 754 974 725

标签: c pointers compiler-construction char


【解决方案1】:

当您通过char * 指针写入结构时,您将45 存储在结构的第一个字节中。如果您使用的是 Little-Endian 实现,您将写入b.a 的低端。如果您使用的是 Big-Endian 实现,您将写入b.a 的高端。

下面是在分配*p=45 之前和之后使用 16 位整数实现的结构通常发生的可视化。请注意,该结构被填充为sizeof(int) 的倍数。

小端序

之前:[05][00] (int)5 c [61] [ ] 之后:[2d][00] (int)45 c [61] [ ]

大端序

之前:[00][05] (int)5 c [61] [ ] 之后:[2d][05] (int)11525 c [61] [ ]

对于更大的整数,有更多的字节排序方式,但在现实生活中你极不可能遇到上述两种方式。

然而,下一行调用未定义的行为有两个原因:

printf("%d " , b.a);
  1. 您正在通过不同类型的指针修改b.a 的一部分。这可能b.a一个“陷阱表示”,读取包含陷阱表示的值会导致未定义的行为。 (不,您在现实生活中不太可能遇到陷阱表示(整数类型)。)
  2. 您正在调用一个没有函数声明的可变参数函数。可变参数函数通常具有不常用的参数传递方式,因此编译器必须了解它。通常的解决方法是#include <stdio.h>

未定义的行为意味着任何事情都可能发生,例如打印错误的值、使您的程序崩溃或(其中最糟糕的情况)完全按照您的预期行事。

【讨论】:

  • 如何知道它是大端还是小端?
  • 如果结果是 45,你就是小端序
  • 是的,但是有什么方法可以检查吗?
  • 我为我的答案编辑了一个测试程序。但是你可以阅读你的 cpu 的文档
  • unsigned char 拥有特权。您可以在int 中存储一个小数字,例如1,并检查(通过unsigned char *)第一个字节是0 还是1,而不会导致未定义的行为。如果是0,就是BE。
【解决方案2】:

你的结构看起来像小端:

00000101 00000000 00000000 00000000 01100001

所以p 指向5 并覆盖它。在 printf 4 little endian 字节打印45

如果你想在大端上尝试754974725 将是结果,因为p 指向 int 的 MSB 端。

一个简单的测试程序来确定你是在小端还是大端:

int main()
{
  int a = 0x12345678;
  unsigned char *c = (unsigned char*)(&a);
  if (*c == 0x78)
    printf("little-endian\n");
  else
    printf("big-endian\n");
  return 0;
}

【讨论】:

    【解决方案3】:

    C 标准保证结构的第一个成员的地址就是结构的地址。也就是说,在你的情况下,

    int* p =(int*)&b;

    一个安全的演员表。但是没有标准的方法可以从结构的地址访问char 成员。这是因为标准没有说明内存中连续成员的连续性:实际上编译器可能会也可能不会在成员之间插入间隙(称为结构封装)以适应芯片组。

    所以你所做的基本上是未定义的。

    【讨论】:

    • 你是正确的标准方式 - 但实际上在 x86 机器上,指针用于访问 b 结构的内存空间的开头。
    • 如果我在火星着陆器上工作(1/2 百万行 C),我不会对“实际”两句,关于标准。
    【解决方案4】:

    因为,这个

    *p = 45;
    

    将 p 指向的值更改为 45。然后你让 p 指向 b。

    【讨论】:

      【解决方案5】:

      如果您希望指针作为结构成员而不是字符数组。 试试这个..

      #include<stdio.h>
      
      struct s{
      int a;
      char *c;
      };
      
      int main()
      {
          struct s b = {5, "a"};
      
          printf("%d %s", b.a, b.c);
          return 0;
      }
      

      【讨论】:

        【解决方案6】:

        试试这个指向结构的指针。

        #include<stdio.h>
        
            struct s{
            int a;
            char c[1];
            };
        
            int main()
            {
                struct s *p;
                struct s b = {5, 'a'};
                p = &b;
        
                printf("%d %s", p->a, p->c);
                return 0;
            }
        

        【讨论】:

          【解决方案7】:

          char *p = (char*) &amp;b; - 在这一行中,p 指向 b 结构的开头,作为 char 指针。

          *p = 45;将45写入b的内存空间,b.a也可以访问。

          当您打印printf("%d ", b.a); 时,您将打印存储在分配为a struct b 成员的堆栈内存中的 45,您将得到 45。

          尝试自己调试它,你会在监视窗口中看到它

          【讨论】:

          • p指向b的第一个字节,但b.ab的前4个字节
          • @mch - 因为 45 只占用 1 个字节,然后打印 b 的前 4 个字节会得到相同的结果
          • @user3805652 我没有关注你更新的问题 - 这太令人困惑了
          猜你喜欢
          • 2018-04-10
          • 1970-01-01
          • 2019-10-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-16
          • 2011-04-05
          • 2014-08-28
          相关资源
          最近更新 更多