【问题标题】:C buffers and confusion, beginner [duplicate]C缓冲区和混乱,初学者[重复]
【发布时间】:2018-12-22 21:26:49
【问题描述】:

我今天一直在搞乱 c,当我注释掉这段代码中的第三个缓冲区时,我不明白输出的差异:

 #include <unistd.h>
 #include <string.h>
 #include <stdio.h>
 void main() {
     unsigned char letters[10];
    memset(letters, 0x00, 10);
    memset(letters, 0x41, 10);
    printf(letters);
    printf(" Total buffer len: %d bytes\n",strlen(letters));

     char nletters[10];
    memset(nletters, 0x00, 10);
    memset(nletters, 0x42, 10);
     printf(nletters);
    printf(" Total buffer len: %d bytes\n",strlen(nletters));

     int nums[10];
     memset(nums, 0x00, 10);
    memset(nums, 0x43, 10);
    printf(nums);
    printf(" Total buffer len: %d bytes\n",strlen(nums));   
 return 0;
}

不同之处在于移除了 nums 缓冲区周围的 cmets:

AAAAAAAAAA�7ǝ�U Total buffer len: 16 bytes
BBBBBBBBBBAAAAAAAAAA�7ǝ�U Total buffer len: 26 bytes

并保留缓冲区:

AAAAAAAAAA Total buffer len: 10 bytes
BBBBBBBBBBAAAAAAAAAA Total buffer len: 20 bytes
CCCCCCCCCC��U Total buffer len: 14 bytes

我没有得到的是:

  1. 看在上帝的份上,注释掉第三个缓冲区会如何影响其他缓冲区的大小?

  2. 缓冲区末尾的额外字节是什么?如何丢失/管理它们(如果我选择连接缓冲区)?

  3. 为什么选择是否注释第三个缓冲区时,打印的缓冲区大小和初始化的大小不一致?

  4. 缓冲区 2 应该是 10 个字节,为什么是 20 个?我不希望是20,我只要求10。我不认为这是不合理的。

【问题讨论】:

  • 欢迎来到软件工程。我们只支持goodon-topic 提问。 Many sitesdifferent rules。如果存在,请随时将您的问题带到适当的站点。首先搜索现有答案。编辑您的问题以适应网站需求。请don't cross post 未在此处删除您的问题。
  • 您缺少空终止的概念。 C 字符串应该有一个空字符来指示字符串的结尾。如果字符串末尾没有空终止字符,则无法预测程序的行为,因为strlen 正在访问正在传递的数组之外的内存。就 C 而言,提供空终止符是程序员的责任。现在,让我问你:如果最后还需要一个空终止字节,那么存储 10 个真实/用户字符需要多少字节的实际 char/unsigned char 存储?
  • 另外,请考虑sizeof(letters)strlen(letters) 之间的区别。前者是一个编译时常量,它转到letters实际声明(并且只给出不考虑或考虑空终止字符的原始大小),而后者是运行时搜索空终止字符以查找存储在(可能)足够大的空间中的实际字符串(没有空字符)的长度。
  • 编译时,始终启用警告,然后修复这些警告,(对于 gcc,至少使用:-Wall -Wextra -Wconversion -pedantic -std=gnu11)除其他外,(无论 Visual Studio 允许什么) functionmain() 的返回类型始终为 int,而不是 void。为什么 main() 被声明为返回 void 但包含语句:return 0;
  • 这是重复的:该用户在几个 SO 网站上发布了完全相同的问题,但用户名略有不同

标签: c string printf c-strings


【解决方案1】:
  1. 在构建栈帧时,一些辅助数据被压入栈中,它可能包含也可能不包含零,最终标志着溢出缓冲区的结束
  2. C 字符串以零标记结束。您的前两个缓冲区不以零结尾,但 CPU 是专用的并读取内存,直到它实际找到零。通常会有一个地方......
  3. 未初始化的缓冲区包含以前使用时留在内存中的任何数据
  4. 堆栈在内存中“向下”增长,因此第一个缓冲区结束于地址“50”,第二个缓冲区结束于地址“40”,第三个缓冲区结束于地址“0”。但是,当您从地址“40”开始打印第二个缓冲区时,它会向上读取内存,即 10 个 B-s、10 个 A-s 和一些字节,直到找到零。

读数:null-terminated stringbuffer overflowstack things

C 字符串在字符串末尾需要一个额外的 \0 字符,因此您的 'letters' 和 'nletters' 可以存储 9 个字母的实际字符串,加上零终止符(内存中不存在默认,你必须自己把它放在那里)。 'nums' 是一个整数数组,它并不适合存储字符串,但 C/C++ 不会阻止你这样做。这就是为什么我在上面写“40”作为第二个缓冲区的假设地址:“nums”很可能是一个 32 位整数的 4x10 字节缓冲区。

【讨论】:

    【解决方案2】:

    以下提议的代码纠正了 OP 发布的代码中的许多(大部分)问题。

    注意main()函数签名的正确声明

    注意代码缩进的一致使用

    注意使用适当的水平间距以提高可读性

    注意在每次调用printf()时使用正确的格式参数

    注意使用 sizeof 返回缓冲区的大小(根据 printf 语句声称它们显示的内容

    请注意,sizeofstrlen() 都返回 size_t,而不是 int

    注意 magic 数字的消除(如 10)

    注意去掉头文件那些内容没有用到的

    现在,建议的代码:

    #include <string.h>
    #include <stdio.h>
    
    #define MAX_LEN 10
    
    
    
    int main( void )
    {
        unsigned char letters[ MAX_LEN ];
        memset( letters, 0x00, sizeof( letters ) );
        memset( letters, 0x41, sizeof( letters )-1 );  // keep NUL byte at end
        printf( "%s\n", letters );   //format the output,
                                     // use \n so immediately output to terminal
        printf( " Total buffer len: %lu bytes\n", sizeof(letters) );
    
        char nletters[ MAX_LEN ];
        memset( nletters, 0x00, sizeof( nletters ) );
        memset( nletters, 0x42, sizeof( nletters )-1 );  // keep NUL byte at end
        printf( "%s\n", nletters );   // format the output,
                                      // use \n so immediately output to terminal
        printf( " Total buffer len: %lu bytes\n", sizeof(nletters) );
    
        int nums[ MAX_LEN ];            // 10 integers
        memset( nums, 0x00, 10* sizeof( int ) );
        memset( nums, 0x43, 9 );  // this only sets first 10 bytes
                                  // NOTE:  sizeof( int ) not same as size of char
                                  //   so most of array not modified
        for( size_t i=0; i< MAX_LEN; i++ )
        {
            printf( "%d\n", nums[ i ] );
        }
        printf( " Total buffer len: %lu bytes\n", sizeof(nums) );
        return 0;
    }
    

    运行上述代码会产生以下输出:

    AAAAAAAAA
    Total buffer len: 10 bytes
    BBBBBBBBB
    Total buffer len: 10 bytes
    1128481603
    1128481603
    67
    0
    0
    0
    0
    0
    0
    0
    Total buffer len: 40 bytes
    

    【讨论】:

    【解决方案3】:

    你说你一直在搞乱 C,但这不是 C。这违反了 C 的一些规则。如果你违反 C 的规则,会发生奇怪的事情...我的主要问题是你是:你在读哪本书?因为你现在的那本书不适合你……


    在 C 中,字符串以 '\0' 结尾。由于letters 不是包含'\0' 字符的字符序列,因此它不是字符串,因此您不应将其视为一个字符。如果你想要一个字符串中有十个字符,你实际上需要一个 11 的数组(至少)来为'\0' 让路(你还需要在memset 之后手动完成)。

    char letters[11];
    memset(letters, 'a', 10);
    letters[10] = '\0';
    

    在 C 中,%zu 格式说明符用于打印size_t 值,例如从strlen 返回的值。 %d 用于打印int 值,

    printf("%s\n", letters);
    printf("strlen(letters): %zu\n", strlen(letters));
    

    看在上帝的份上,注释掉第三个缓冲区会如何影响其他缓冲区的大小?

    printfstrlen 期望它们的参数是字符串,但是字符串 必须始终包含'\0'。您的数组不包含 '\0',因此 与字符串相关的 函数会循环越界并因此处理越界的数据。

    作为练习,预测strlen((char[]) { 1, 2, 3, 4, '\0', 5 })... 测试你的理论。


    缓冲区末尾的额外字节是什么?如何丢失/管理它们(如果我选择连接缓冲区)?

    那些额外的字节是未定义的行为,这是一组可怕的词,意思是“任何事情都可能发生,因为你违反了规则”......当你打破 C 语言的规则时,会发生奇怪的事情...


    为什么我选择是否注释第三个缓冲区时打印的缓冲区大小和初始化的大小不一致?

    再次,未定义的行为,违反规则...奇怪的事情发生...你在读哪本书?我问的原因,似乎是任何人使用一本书会很快解决这个问题,所以我认为你只是 猜测(这在 C 中很危险)。通过阅读一本书,您将更快地学习 C(如正确)。


    缓冲区 2 应该是 10 个字节,为什么是 20 个?我不希望是20,我只要求10。我不认为这是不合理的。

    当没有字符串时,停止告诉 C 有一个字符串(导致'\0' 的字符序列)...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-30
      • 2014-05-04
      • 1970-01-01
      • 2013-07-04
      • 1970-01-01
      • 1970-01-01
      • 2021-01-06
      • 1970-01-01
      相关资源
      最近更新 更多