精华内容
下载资源
问答
  • [img=https://img-bbs.csdn.net/upload/201702/05/1486264943_93138.png][/img][img=https://img-bbs.csdn.net/upload/201702/05/1486264952_608530.png][/img]
  • CUDA未定义标识符"printf": 引用头文件stdio.h: #include <stdio.h>

    CUDA未定义标识符"printf":
    在这里插入图片描述
    引用头文件stdio.h:

    #include <stdio.h>
    
    展开全文
  • 但是事情好像还没有结束,我们都知道:在调用系统中的 printf 语句时,传入的参数个数和类型不是固定的,那么 printf 中是如何来动态侦测参数的个数和类型的呢? 四、C语言中的可变参数 在 C 语言中实现可变参数需要...

    864435abefdcf78985a2ddcd366e58c4.png

    这是道哥的第015篇原创

    关注+星标公众号,不错过最新文章

    一、前言

    1. 为什么写这篇文章

    在上周六,我在公众号里发了一篇文章:C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻,以直白的语言、一目了然的图片来解释指针的底层逻辑,有一位小伙伴对文中的代码进行测试,发现一个比较奇怪的问题。我把发来的测试代码进行验证,思考好久也无法解释为什么会出现那么奇怪的打印结果。

    为了整理思路,我到阳台抽根烟。晚上的风很大,一根烟我抽了一半,风抽了一半,可能风也有自己的烦恼。后来一想,烟是我买的,为什么让风来抽?于是我就开始抽风!不对,开始回房间继续抽代码,我就不信,这么简单的 printf 语句,怎么就搞不定?!

    于是就有了这篇文章。

    2. 你能得到什么收获

    1. 函数参数的传递机制;
    2. 可变参数的实现原理(va_list);
    3. printf 函数的实现机制;
    4. 面对问题时的分析思路。

    友情提醒:文章的前面大部分内容都是在记录思考问题、解决问题的思路,如果你对这个过程不感兴趣,可以直接跳到最后面的第四部分,用图片清晰的解释了可变参数的实现原理,看过一次之后,保管你能深刻记住。

    3. 我的测试环境

    3.1 操作系统

    每个人的电脑环境都是不一样的,包括操作系统、编译器、编译器的版本,也许任何一个小差别都会导致一些奇奇怪怪的的现象。不过大部分人都是使用 Windows 系统下的 VS 集成开发环境,或者 Linux 下的 gcc 命令行窗口来测试。

    我一般都是使用 Ubuntu16.04-64 系统来测试代码,本文中的所有代码都是在这个平台上测试的。如果你用 VS 开发环境中的 VC 编译器,可能在某些细节上与我的测试结果又出入,但是问题也不大,遇到问题再分析,毕竟解决问题也是提升自己能力的最快途径。

    3.2 编译器

    我使用的编译器是 Ubuntu16.04-64 系统自带的版本,显示如下:

    d1422087786f81f26f4b4145d6bf4931.png

    另外,我安装的是 64 位系统,为了编译 32 位的可执行程序,我在编译指令中添加了 -m 选项,编译指令如下:

    gcc -m32 main.c -o main

    使用 file main 命令来查一下编译得到的可执行文件:

    16d584dec79001ff7cdae042b65d7394.png

    所以,在测试时如果输出结果与预期有一些出入,先检查一下编译器。C 语言本质上都是一些标准,每家的编译器都是标准的实现者,只要结果满足标准即可,至于实现的过程、代码执行的效率就各显神通了。

    二、问题导入

    1. 网友求助代码

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct 
    {
        int age;
        char name[8];
    } Student;
    
    int main()
    {
        Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}};
        Student *p = &(s[0]);
        printf("%d, %d n", *s, *p);
    }

    2. 期望结果

    根据上篇文章的讨论,我们知道:

    1. s 是一个包含 3 个元素数组,每个元素的类型是结构体 Student;
    2. p 是一个指针,它指向变量s,也就是说指针 p 中保存的是变量 s 的地址,因为数组名就表示该数组的首地址。

    既然 s 也是一个地址,它也代表了这个数组中第一个元素的首地址。第一个元素类型是结构体,结构体中第一个变量是 int 型,因此 s 所代表的那个位置是一个 int 型数据,对应到示例代码中就是数字 1。因此 printf 语句中希望直接把这个地址处的数据当做一个 int 型数据打印出来,期望的打印结果是:1, 1

    这样的分析过程好像是没有什么问题的。

    3. 实际打印结果

    我们来编译程序,输出警告信息:

    11533db1ea4582135b9834be1f749cf7.png

    警告信息说:printf 语句需要 int 型数据,但是传递了一个 Student 结构体类型,我们先不用理会这个警告,因为我们就是想通过指针来访问这个地址里的数据。

    执行程序,看到实际打印结果是:1, 97,很遗憾,与我们的期望不一致!

    三、分析问题的思路

    1. 打印内存模型

    可以从打印结果看,第一个输出的数字是 1,与预期符合;第二个输出 97,很明显是字符 'a' 的 ASCII 码值,但是 p 怎么会指到 name 变量的地址里呢?

    首先确认 3 个事情:

    1. 结构体 Student 占据的内存大小是多少?
    2. 数组 s 里的内存么模型是怎样的?
    3. s 与 指针变量 p 的值是否正确?

    把代码改为如下:

    Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}};
    Student *p = s;
    
    printf("sizeof Student = %d nn", sizeof(Student));
    
    printf("print each byte in s: ");
    char *pTmp = p;
    for (int i = 0; i < 3 * sizeof(Student); i++)
    {
       if (0 == i % sizeof(Student))
            printf("n");
       printf("%x ", *(pTmp + i));
    }
    printf("nn");
    
    printf("print value of s and p nn");
    printf("s = 0x%x, p = 0x%x nn", s, p);
    
    printf("%d, %d n", *s, *p);

    我们先画一下数组 s 预期的内存模型,如下:

    02387d39302750312776932de485f4fc.png

    编译、测试,打印结果如下:

    fdd5746025a0026959ace8a7b76def3e.png

    从打印结果看:

    1. 结构体 Student 占据 12 个字节,符合预期。
    2. 数组 s 的内存模型也是符合预期的,一共占据 36 个字节。
    3. s 与 p 都代表一个地址,打印结果它俩相同,也是符合预期的。

    那就见鬼了:既然 s 与 p 代表同一个内存地址,但是为什么用 *p 读取 int 型数据时,得到的却是字符 'a' 的值呢?

    2. 分开打印信息

    既然第一个 *s 打印结果是正确的,那么就把这个两个数据分开来打印,测试代码如下:

    Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}};
    Student *p = s;
    
    printf("%d n", *s);
    printf("%d n", *p);

    编译、测试,打印结果如下:

    a5649116b43af5f81cedb6c3e79753b3.png

    打印结果符合预期!也就是说分成两条打印语句是可以正确读取到目标地址里的 int 型数据的,但是在一条语句里就不行!

    其实此时,可以判断出大概是 printf 语句的原因了。从现象上看,似乎是 printf 语句在执行过程中打印第一个数字之后,影响到了指针 p 的值,但是具体是怎么影响的说不清楚,而且它是系统里的库函数,肯定不能改变 p 的值。

    于是在 google 中搜索关键字:"glibc printf bug",你还别说,真的搜索到很多相关资料,但是浏览了一下,没有与我们的测试代码类似的情况,还得继续思考。

    3. 一步步分析问题本质原因

    3.1 打印一个最简单的字符串

    既然是因为在 printf 语句中打印 2 个数据才出现问题,那么我就把问题简化,用一个最简单的字符串来测试,代码如下:

    char aa[] = "abcd";
    char *pc = aa;
    printf("%d, %d n", *pc, *pc);

    编译、执行,打印结果为:"97, 97",非常正确!这就说明 printf 语句在执行时没有改变指针变量的指向地址。

    3.2 打印一个结构体变量

    既然在字符串上测试没有问题,那么问题就出在结构体类型上了。那就继续用结构体变量来测试,因为上面的测试代码是结构体变量的数组,现在我们把数组的影响去掉,只对单独的一个结构体变量进行测试:

    Student s = {1, "a"};
    
    printf("%d n", s); 
    printf("%d, %d n", s, s);

    注意:这里的 s 是一个变量,不是数组了,所以打印时就不需要用 * 操作符了。编译、执行,输出结果:

    85dcf5d559b42a56e1eff285cfb402b5.png

    输出结果与之前的错误一样,至此可以得出结论:问题的原因至少与数组是没有关系的!

    现在测试的结构体中有 2 个变量:age 和 name,我们继续简化,只保留 int 型数据,这样更容易简化问题。

    3.3 测试更简单的结构体变量

    测试代码如下:

    typedef struct _A
    {
       int a;
       int b;
       int c;
    }A;
    
    int main()
    {
        A a = {10, 20, 30};
        printf("%d %d %d n", a, a, a);
    }

    编译、执行,打印结果为:10 20 30,把 3 个成员变量的值都打印出来了,太诡异了!好像是在内存中,从第一个成员变量开始,自动递增然后获取 int 型数据。

    于是我就把后面的两个参数 a 去掉,测试如下代码:

    A a = {10, 20, 30};
    printf("%d %d %d n", a);

    编译、执行,打印结果仍然为:10 20 30!这个时候我快疯掉了,主要是时间太晚了,我不太喜欢熬夜。

    于是大脑开始偷懒,再次向 google 寻求帮助,还真的找到这个网页:https://stackoverflow.com/questions/26525394/use-printfs-to-print-a-struct-the-structs-first-variable-type-is-char。感兴趣的小伙伴可以打开浏览一下,其中有下面这两段话说明了重点:

    b1d22b8e91dd920c70420f40512f7197.png

    a27fe217449fcb1449bfb076e1bd57e3.png

    一句话总结:用 printf 语句来打印结构体类型的变量,结果是 undefined behavior!什么是未定义行为,就是说发生任何状况都是可能的,这个就要看编译器的实现方式了。

    看来,我已经找到问题的原因了:原来是因为我的知识不够扎实,不知道打印结构体变量是未定义行为。

    补充一点心得: 1. 我们在写程序的时候,因为脑袋中掌握的大部分知识都是正确的,因此编写的代码大部分也都是与预期符合的,不可能故意去写一些稀奇古怪的代码。就比如打印结构体信息,一般正常的思路都是把结构体里面的成员变量,按照对应的数据类型来打印输出。 2. 但是偶尔也会犯低级错误,就像这次遇到的问题一样:直接打印一个结构体变量。因为发生错误了,所以才了解到原来直接打印结构体变量,是一个未定义行为。当然了,这也是一个获取知识的途径。

    追查到这里,似乎可以结束了。但是我还是有点不死心,既然是未定义的行为,那么为什么每次打印输出的结果都错的这么一致呢?既然是由编译器的实现决定的,那么我使用的这个 gcc 版本内部是怎么来打印结构体变量的呢?

    于是我继续往下查...

    3.4 继续打印结构体变量

    刚才的结构体 A 中的成员都是 int 型,每个 int 数据在内存中占据 4 个字节,所以刚才打印出的数据恰好是跨过 4 个字节。如果改成字符串型,打印时是否也会跨过4个字节,于是把测试代码改成下面这样:

    typedef struct _B
    {
       int a;
       char b[12];
    }B;
    
    int main()
    {
        B  b = {10, "abcdefgh"};
        printf("%d %c %c n", b);
    }

    编译、执行,打印结果如下:

    8229ad0edb5b46c21a8e42fcb70d9849.png

    果然如此:字符 a 与数字 10 之间跨过 4 个直接,字符 e 与 a 之间也是跨过 4 个字节。那就说明 printf 语句在执行时可能是按照 int 型的数据大小(4个字节)为单位,来跨越内存空间,然后再按照百分号%后面的字符来读取内存地址里的数据。

    那就来验证这个想法是否正确,测试代码如下:

    Student s = {1, "aaa"};
    char *pTmp = &s;
    for (int i = 0;i < sizeof(Student); i++)
    {
       printf("%x ", *(pTmp + i));
    }
    
    printf("n");
    printf("%d, %x n", s);

    编译、执行,打印结果为:

    ada4b46274ad2f81cc43cdc1535b8e68.png

    输出结果确实如此:数字 1 之后的内存中存放的是 3 个字符 'a',第二个打印数据格式是 %x,所以就按照整型数据来读取,于是得到十六进制的616161。

    至此,我们也知道了 gcc 这个版本中,是如何来操作这个 “undefined behavior” 的。但是事情好像还没有结束,我们都知道:在调用系统中的 printf 语句时,传入的参数个数和类型不是固定的,那么 printf 中是如何来动态侦测参数的个数和类型的呢?

    四、C语言中的可变参数

    在 C 语言中实现可变参数需要用到这下面这几个数据类型和函数(其实是宏定义):

    1. va_list
    2. va_start
    3. va_arg
    4. va_end

    处理动态参数的过程是下面这 4 个步骤:

    1. 定义一个变量 va_list arg;
    2. 调用 va_start 来初始化 arg 变量,传入的第二个参数是可变参数(三个点)前面的那个变量;
    3. 使用 va_arg 函数提取可变参数:循环从 arg 中提取每一个变量,最后一个参数用来指定提取的数据类型。比如:如果格式化字符串是 %d,那么就从可变参数中提取一个 int 型的数据,如果格式化字符串是 %c,就从可变参数中提取一个 char 型数据;
    4. 数据处理结束后,使用 va_end 来释放 arg 变量。

    文字表达起来好像有点抽象、复杂,先看一下下面的 3 个示例,然后再回头看一下上面这 4 个步骤,就容易理解了。

    1. 利用可变参数的三个函数示例

    示例1:参数类型是 int,但是参数个数不固定

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    
    void my_printf_int(int num,...)
    {
        int i, val;
        va_list arg;
        va_start(arg, num);
        for(i = 0; i < num; i++)
        {
            val = va_arg(arg, int);
            printf("%d ", val);
        }
        va_end(arg);
        printf("n");
    }
    
    int main()
    {
        int a = 1, b = 2, c = 3;
        my_printf_int(3, a, b, c);
    }

    编译、执行,打印结果如下:

    4a5548193ad56b78ca23e31afc4ba5b2.png

    示例2:参数类型是 float,但是参数个数不固定

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    
    void my_printf_float (int n, ...)
    {
      int i;
      double val;
      va_list vl;
      va_start(vl,n);
      for (i = 0; i < n; i++)
      {
        val = va_arg(vl, double);
        printf ("%.2f ",val);
      }
      va_end(vl);
      printf ("n");
    }
    
    int main()
    {
        float f1 = 3.14159, f2 = 2.71828, f3 = 1.41421;
        my_printf_float (3, f1, f2, f3);
    }

    编译、执行,打印结果如下:

    84a9e7faed345d81648d46ee435ae3db.png

    示例3:参数类型是 char*,但是参数个数不固定

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    
    void my_printf_string(char *first, ...)
    {
        char *str = first;
        va_list arg;
        va_start(arg, first);
        do 
        {
            printf("%s ", str);
            str = va_arg(arg, char*);
        } while (str != NULL );
        va_end(arg);
        printf("n");
    }
    
    int main()
    {
        char *a = "aaa", *b = "bbb", *c = "ccc";
        my_printf_string(a, b, c, NULL);
    }

    编译、执行,打印结果如下:

    82ef56f4843fda6ffee0eedb73ee3a7c.png

    注意:以上这3个示例中,虽然传入的参数个数是不固定的,但是参数的类型都必须是一样的!

    另外,处理函数中必须能够知道传入的参数有多少个,处理 int 和 float 的函数是通过第一个参数来判断的,处理 char* 的函数是通过最后一个可变参数NULL来判断的。

    2. 可变参数的原理

    2.1 可变参数的几个宏定义

    typedef char *    va_list;
    
    #define va_start  _crt_va_start
    #define va_arg    _crt_va_arg  
    #define va_end    _crt_va_end  
    
    #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )  
    #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  
    #define _crt_va_end(ap)      ( ap = (va_list)0 )

    注意:va_list 就是一个 char* 型指针。

    2.2 可变参数的处理过程

    我们以刚才的示例 my_printf_int 函数为例,重新贴一下:

    void my_printf_int(int num, ...) // step1
    {
        int i, val;
        va_list arg;
        va_start(arg, num);         // step2
        for(i = 0; i < num; i++)
        {
            val = va_arg(arg, int); // step3
            printf("%d ", val);
        }
        va_end(arg);                // step4
        printf("n");
    }
    
    int main()
    {
        int a = 1, b = 2, c = 3;
        my_printf_int(3, a, b, c);
    }

    Step1: 函数调用时

    C语言中函数调用时,参数是从右到左、逐个压入到栈中的,因此在进入 my_printf_int 的函数体中时,栈中的布局如下:

    7bbec354416d275e665370781e6120a5.png

    Step2: 执行 va_start

    va_start(arg, num);

    把上面这语句,带入下面这宏定义:

    #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

    宏扩展之后得到:

    arg = (char *)num + sizeof(num);

    结合下面的图来分析一下:首先通过 _ADDRESSOF 得到 num 的地址 0x01020300,然后强转成 char* 类型,再然后加上 num 占据的字节数(4个字节),得到地址 0x01020304,最后把这个地址赋值给 arg,因此 arg 这个指针就指向了栈中数字 1 的那个地址,也就是第一个参数,如下图所示:

    96a7a562b7d4a7be5e5a0fa9eb940f1a.png

    Step3: 执行 va_arg

    val = va_arg(arg, int);

    把上面这语句,带入下面这宏定义:

    #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

    宏扩展之后得到:

    val = ( *(int *)((arg += _INTSIZEOF(int)) - _INTSIZEOF(int)) )

    结合下面的图来分析一下:先把 arg 自增 int 型数据的大小(4个字节),使得 arg = 0x01020308;然后再把这个地址(0x01020308)减去4个字节,得到的地址(0x01020304)里的这个值,强转成 int 型,赋值给 val,如下图所示:

    c2f5cf2d18ca515358b61adbd762fbf6.png

    简单理解,其实也就是:得到当前 arg 指向的 int 数据,然后把 arg 指向位于高地址处的下一个参数位置。

    va_arg 可以反复调用,直到获取栈中所有的函数传入的参数。

    Step4: 执行 va_end

    va_end(arg);

    把上面这语句,带入下面这宏定义:

    #define _crt_va_end(ap)      ( ap = (va_list)0 )

    宏扩展之后得到:

    arg = (char *)0;

    这就好理解了,直接把指针 arg 设置为空。因为栈中的所有动态参数被提取后,arg 的值为 0x01020310(最后一个参数的上一个地址),如果不设置为 NULL 的话,下面使用的话就得到未知的结果,为了防止误操作,需要设置为NULL。

    3. printf利用可变参数打印信息

    理解了 C 语言中可变参数的处理机制,再来思考 printf 语句的实现机制就很好理解了。

    3.1 GNU 中的 printf 代码

    __printf (const char *format, ...)
    {
       va_list arg;
       int done;
    
       va_start (arg, format);
       done = vfprintf (stdout, format, arg);
       va_end (arg);
    
       return done;
    }

    可见,系统库中的 printf 也是这样来处理动态参数的,vfprintf 函数最终会调用系统函数 sys_write,把数据输出到 stdout 设备上(显示器)。 vfprintf 函数代码看起来还是有点复杂,不过稍微分析一下就可以得到其中的大概实现思路:

    1. 逐个比对格式化字符串中的每一个字符;
    2. 如果是普通字符就直接输出;
    3. 如果是格式化字符,就根据指定的数据类型,从可变参数中读取数据,输出显示;

    以上只是很粗略的思路,实现细节肯定复杂的多,需要考虑各种细节问题。下面是 2 个简单的示例:

    void my_printf_format_v1(char *fmt, ...)
    {
        va_list arg;
        int d;
        char c, *s;
    
       va_start(arg, fmt);
        while (*fmt) 
        {
            switch (*fmt) {
                case 's': 
                    s = va_arg(arg, char *);
                    printf("%s", s);
    
                    break;
    
                case 'd':  
                    d = va_arg(arg, int);
                    printf("%d", d);
                    break;
    
                case 'c':      
                    c = (char) va_arg(arg, int);
                    printf(" %c", c);
                    break;
                default:
                    if ('%' != *fmt || ('s' != *(fmt + 1) && 'd' != *(fmt + 1) && 'c' != *(fmt + 1)))
                        printf("%c", *fmt);
                    break;
            }
            fmt++;
        }
        va_end(arg);
    }
    
    int main()
    {
        my_printf_format_v1("age = %d, name = %s, num = %d n", 
            20, "zhangsan", 98);
    }

    编译、执行,输出结果:

    63522c4a26a947efe5449d095f3d0b91.png

    完美!但是再测试下面代码(把格式化字符串最后面的 num 改成 score):

    my_printf_format_v1("age = %d, name = %s, score = %d n", 
            20, "zhangsan", 98);

    编译、执行,输出结果:

    4fecc785853eb2a0bcb9a019ceb6827c.png

    因为普通字符串 score 中的字符 s 被第一个 case 捕获到了,所以发生错误。稍微改进一下:

    void my_printf_format_v2(char *fmt, ...)
    {
        va_list arg;
        int d;
        char c, lastC = '0', *s;
    
       va_start(arg, fmt);
        while (*fmt) 
        {
            switch (*fmt) {
                case 's': 
                    if ('%' == lastC)
                    {
                        s = va_arg(arg, char *);
                        printf("%s", s);
                    }
                    else
                    {
                       printf("%c", *fmt);
                    }
                    break;
    
                case 'd':  
                    if ('%' == lastC)
                    {
                        d = va_arg(arg, int);
                        printf("%d", d);
                    } 
                    else
                    {
                       printf("%c", *fmt);
                    }
                    break;
    
                case 'c':   
                    if ('%' == lastC)
                    {    
                        c = (char) va_arg(arg, int);
                        printf(" %c", c);
                    }
                    else
                    {
                        printf("%c", *fmt);
                    }
    
                    break;
                default:
                    if ('%' != *fmt || ('s' != *(fmt + 1) && 'd' != *(fmt + 1) && 'c' != *(fmt + 1)))
                        printf("%c", *fmt);
                    break;
            }
            lastC = *fmt;
            fmt++;
        }
        va_end(arg);
    }
    
    int main()
    {
        my_printf_format_v2("age = %d, name = %s, score = %d n", 
            20, "zhangsan", 98);
    }

    编译、执行,打印结果:

    f4a9d25772be6d2749198a0537068efd.png

    五、总结

    我们来复盘一下上面的分析过程,开头的第一个代码本意是测试关于指针的,结果到最后一直分析到 C 语言中的可变参数问题。可以看出,分析问题-定位问题-解决问题是一连串的思考过程,把这个过程走一遍之后,理解才会更深刻。

    我还有另外一个感受:如果我没有写公众号,就不会写这篇文章;如果不写这篇文章,就不会研究的这么较真。也许在中间的某个步骤,我就会偷懒对自己说:理解到这个层次就差不多了,不用继续深究了。所以说以文章的形式来把自己的思考过程进行输出,是技术提升是非常有好处的,也强烈建议各位小伙伴尝试一下这么做。

    而且,如果这些思考过程能得到你们的认可,那么我就会更有动力来总结、输出文章。因此,如果这篇总结对你能有一丝丝的帮助,请转发、分享给你的技术朋友,在此表示衷心的感谢!

    另外,文中如果有错误,非常欢迎在留言区一起讨论。或者添加我的个人微信,这样沟通就更及时、有效率。

    祝你好运!

    ---

    我会把十多年嵌入式开发中的项目实战经验进行输出总结!

    如果觉得文章不错,请转发、分享给您的朋友,您的支持是我持续写作的最大动力!

    转载:欢迎转载,但未经作者同意,必须保留此段声明,必须在文章中给出原文连接。

    推荐阅读

    [1] C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻
    [2] 一步步分析-如何用C实现面向对象编程
    [3] 原来gdb的底层调试原理这么简单
    [4] 生产者和消费者模式中的双缓冲技术
    [5] 关于加密、证书的那些事
    [6] 深入LUA脚本语言,让你彻底明白调试原理

    展开全文
  • <p>void printf(int *pbegin,int *pend) <p>{<!-- -->  while(pbegin != pend)  {<!-- -->  cout<<*pbegin;  ++pbegin;  } <p>}   <p>void printf(char *pbegin,char *...
  • VS2019中,未定义标识符:__STDC__

    千次阅读 2020-03-29 17:05:50
    运行环境:VS2019 问题源码: #include <stdio.h> //菜鸟教程 main() { printf("file :s\n", __FILE__ ); printf("date :s\n", __DATE__ );... printf("Time :s\n", __TIME__ );... printf(...

    运行环境:VS2019

    问题源码:

    #include <stdio.h>	//菜鸟教程
    
    main() {
    	printf("file :s\n", __FILE__ );
    	printf("date :s\n", __DATE__ );
    	printf("Time :s\n", __TIME__ );
    	printf("Line :d\n", __LINE__ );
    	printf("ANSI :d\n", __STDC__ );
    
    }
    

    错误描述:

    错误(活动) E0020 未定义标识符 "__STDC__"

    是否解决

    未解决,准备在GCC中再运行一遍。

    菜鸟教程学习预处理器时,遇到了这个问题.

    -------------------------------2020年3月29日-------------------------------

    展开全文
  • voidmian(){inti=0;intover=1;intstudentnumber=0;...if((fp=fopen("student.dat","w"))==NULL){printf("cannotopenfile\n");return;}printf("请输入你们班有多少...void mian(){int i = 0;int over = 1;...

    voidmian(){inti=0;intover=1;intstudentnumber=0;intdocontrol=0;FILE*fp;if((fp=fopen("student.dat","w"))==NULL){printf("cannotopenfile\n");return;}printf("请输入你们班有多少...

    void mian()

    {

    int i = 0;

    int over = 1;

    int studentnumber = 0;

    int docontrol = 0;

    FILE *fp;

    if ((fp = fopen("student.dat","w"))==NULL)

    {

    printf("cannot open file\n");

    return;

    }

    printf("请输入你们班有多少个学生?:\n");

    scanf("%d",&studentnumber);

    student *stu = new student[studentnumber];

    printf("*****************************\n");

    printf("*请输入学生信息,格式如下*\n");

    printf("*姓名 学号 学生记录 考试记录*\n");

    printf("* wang 200201 0 1 *\n");

    printf("* zhang 200202 0 1 *\n");

    printf("*由于是刚开学,所以初试的学生记录应该为0 *\n");

    printf("*考试记录应该为1*\n");

    printf("*****************************\n");

    printf("请输入: \n");

    for(i=0; i < studentnumber; i++)

    {

    scanf("%s%d%d%d",stu[i].name,&stu[i].num,&stu[i].callSkip,&stu[i].examSkip);

    }

    for (i = 0; i< studentnumber; i++)

    {

    fwrite(&stu[i],sizeof(struct student),1,fp);

    }

    fclose(fp);

    while (over == 1)

    {

    printf("\nl.点名\n");

    printf("2.输出点名三次和三次以上未到学生\n");

    printf("3.输出点名全勤或三次以下未到学生\n");

    printf("4.单个学生查询\n");

    printf("请选择你想进行的操作:");

    scanf("%d",&docontrol);

    printf("\n");

    switch(docontrol)

    {

    case 1:

    call(stu,studentnumber);

    break;

    case 2:

    countskip(stu,studentnumber);

    outputexamstudentskip0(stu,studentnumber);

    break;

    case 3:

    countskip(stu,studentnumber);

    outputexamstudentskip1(stu,studentnumber);

    break;

    case 4:

    countskip(stu,studentnumber);

    searchstudent(stu,studentnumber);

    break;

    default:

    break;

    }

    printf("\n\n是否想结束? y/n:");

    char ch;

    scanf("%c",&ch);

    scanf("%c",&ch);

    // cin>>ch;

    if(ch =='y')

    {

    over = 0;

    }

    else

    {

    over = 1;

    }

    }

    delete[]stu;

    }

    应该要怎样去改啊???求大神打救~~!!

    应该要怎样改啊???

    展开

    展开全文
  • 感谢关注。 我在使用VC2010版本 &#...系统说printf未定义标识符 ,不知道怎么解决,希望有人能帮我解答一下。 <p>PS:之前有人提到的输入#pragma warning(disable:4996)和s_我试了没有用。</p>
  • error C3861: “printf”: 找不到标识符 atoi出现未定义 等等 已经包含了studio.h,但是还是报错,试过直接把stdio.h添加到项目里面,依然无效。 解决方法: VS2017因为生命开发周期(SDL)检查,放弃了原不安全的...
  • __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 ...__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 下面我们给出一个宏函数示例#include #define PRINT_DEBUG printf("%s %d %s %s %d\n",
  • 预处理标识符: __FILE__:当前编辑的源文件路径。 __LINE__:代码执行的行数。 __DATE__:代码执行的日期。 __TIME__:代码执行的时间。 __STDC__:若当前的编译器遵循ASCIC,其值为1,否则显示未定义,会报错。 ...
  • 前些日子为了弄清楚宏定义写了个小程序,忽然发现将标识符定义为空和将标识符定义为未定义完全是两码事……然后发现原来宏定义中还有一种状态叫“未定义”。下面就是那个小程序以及简单的分析,然后通过这个程序可以...
  • 1.VS2013调试时,监视窗口有些变量显示“未定义标识符”,但程序没问题 解决:项目-》属性-》c/c++-》优化-》改为禁用/OD ,然后重新编译 2.vs2013调试时,码流打印出来为空,代码为char *data; printf("%s",data...
  • 错误(活动) 未定义标识符 "contours" ConsoleApplication1 d:\基于视觉的副驾驶座儿童乘坐提醒系统设计程序文档20180417\阈值化处理\ConsoleApplication1\main.cpp 26 错误(活动) 应输入声明 ConsoleApplication1...
  • #include #include #include #include #define MAXWORD 30 /*单词...如果我下面这样写 if (strcmp(p ->count, w) == 0),这里面的w就会报错,提示未定义标识符,次啊面的函数也是这么个情况,请问昨晚那么晚改???
  • 错误信息was not declared in this scope

    千次阅读 2018-12-18 20:17:16
    其含义为标识符在其出现的地方是未被定义的。 出现该错误的时候,会同时把未定义的变量名显示出来。比如如下程序: int main() { printf("%d",i);//这个i是没定义的。 } 这时就会显示一个’i’ was not ...
  • 其含义为标识符在其出现的地方是未被定义的。 出现该错误的时候,会同时把未定义的变量名显示出来。比如如下程序: intmain(){printf("%d",i);//这个i是没定义的。} 就会显示一个'i'was not declared in this ...
  • 预处理详解

    2020-11-24 19:00:22
    __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 这些预定义符号都是语言内置的,比如: printf("file:%s line:%d\n", __FILE__,__LINE__); #define #define定义标识符 语法: #define name stuff 举例:
  • [图片说明](https://img-ask.csdn.net/upload/201804/02/1522644910_216149.png)图片说明](https://img-ask.csdn.net/upload/201804/02/1522644895_659238.png)3 IntelliSense: 未定义标识符 "frame2" c:\Users\颜...
  • 1.我用的是VS2013,配置的是OpenCV3.4.0,运行如下程序的时候就说有许多未定义标识符,琢磨了好久就是不知道哪里出了问题。求大神帮我看看程序。看看哪里出了问题,,需要的话我可以把整个程序发过去,(有偿) ...
  • 1.30如何判断哪些标识符可以使用,哪些被保留了? 初始化  1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零?  1.32 下面的代码为什么不能...
  •  命名空间 1.30如何判断哪些标识符可以使用,哪些被保留了? 初始化 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 1.32 下面的代码...
  • “manager::d4”使用未定义的 class“mu” “manager::d5”使用未定义的 class“mu” “mu”:“class”类型重定义 “stur”: 重定义 意外的标记位于“;”之前 缺少类型...
  • “manager::d4”使用未定义的 class“mu” “manager::d5”使用未定义的 class“mu” “mu”:“class”类型重定义 “stur”: 重定义 意外的标记位于“;”之前 缺少类型...
  • 1.30 如何判断哪些标识符可以使用,哪些被保留了? 44 初始化 47 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 47 1.32 下面的代码为...
  • 1.30 如何判断哪些标识符可以使用,哪些被保留了? 44 初始化 47 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 47 1.32 下面的代码为...
  • 你必须知道的495个C语言问题

    千次下载 热门讨论 2015-05-08 11:09:25
    1.30如何判断哪些标识符可以使用,哪些被保留了? 初始化 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 1.32 下面的代码为什么不能...

空空如也

空空如也

1 2 3
收藏数 41
精华内容 16
关键字:

未定义标识符printf