博客
关于我
深入理解C语言小记
阅读量:576 次
发布时间:2019-03-11

本文共 3254 字,大约阅读时间需要 10 分钟。

Dennis Ritchie近日辞世,他是C语言的发明者之一,这种计算机语言深深影响着整个计算机领域。尽管C语言已经持续40多年,但它依然在技术发展中发挥着重要作用。许多现代语言如C++、Java、C#、Python、JavaScript等都深受C语言的影响。那么,你对C语言了解吗?在本站已有《C语言的谜题》和《谁说C语言很简单?》等相关文章,你或许已经开始接触C语言。那么,本站将为你呈现一篇关于深入理解C语言的文章,一份缅怀Dennis的同时,也将为你提供一些学习C语言的实用建议。

C语言的学习与探索

让我们从经典的代码着手:

int main() {
int a = 42;
printf("%d\n", a);
}

这段代码看似简单,但它缺少了#include <stdio.h>return 0;。戴乐甲在问,你注意到哪些问题呢?在C++中,这段代码无法编译,因为C++要求函数明确声明。然而,在C语言的编译器下,这段代码会顺利通过编译。由于C语言的特点,它不会强制要求标准库的包含。程序的退出码在ANSI-C下尚未明确,但在C89下,它会取printf函数的返回值。printf函数返回3,因为它输出了“4”、“2”和换行符“\n”。值得注意的是,标准C“Amd 1170”(C99)会将退出码设置为0,从而给程序带来更明确的结果。你可以通过gcc -std=c89-std=c99来测试这些编译选项下程序的行为。

再来看下面这段代码:

#include 
void f(void) {
static int a = 3;
static int b;
int c;
++a;
++b;
++c;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
}
int main(void) {
f();
f();
f();
}

程序输出的结果是什么?a的值很可能是4、5、6,因为它是静态变量,每次调用都会递增。对于c来说,它可能是一个随机的数。而b的输出很有可能是1、2、3。与c不同之处在于,b是在每次函数调用时重新初始化的,因为编译器为每个函数调用分配新的栈空间,这显著地影响了性能。相比之下,静态变量(包括c)只会在程序启动时初始化一次。全局变量也有类似的行为。你还注意到,static前缀不仅可以用于局部变量,还可以用于全局变量吗?是的。如果加上static,全局变量会变得无法在外部链接,也就是说,它们只能在当前源文件中使用。

另外一个示例:

#include 
void foo(void) {
int a;
printf("%d\n", a);
}
void bar(void) {
int a = 42;
}
int main(void) {
bar();
foo();
}

这段程序会输出什么?它会输出42,但这是有条件的。在默认的情况下(没有优化),编译器会重用调用栈空间,这样foo()的变量不会再次初始化。你应该知道,未初始化的局部变量的值是未定义的。如果启用了代码优化选项(比如-O),那么foo()会被内联到main()中,这样foo()就不会再被调用,结果将是一个随机值或者未初始化的值。

再来看一个关于运算顺序的示例:

#include 
int b(void) {
printf("3");
return 3;
}
int c(void) {
printf("4");
return 4;
}
int main(void) {
int a = b() + c();
printf("%d\n", a);
}

你会觉得这段程序只可能输出3 + 4 = 7。但实际上,这pletely可能输出4 + 3 = 7。因为在C/C++中,表达式的评估顺序是不确定的,编译器可以选择按左到右的顺序或右到左的顺序。这种不确定性使得程序的行为不可移植。

接下来,让我们讨论一下编译器警告的重要性。考虑以下代码:

#include 
int main(void) {
int a;
printf("%d\n", a);
}

在没有优化的情况下(如cc -Wall),这段代码会成功编译,但会给出 Palestine a warning about uninitialized variable a。然而,在启用代码优化选项(如cc -Wall -O)时,编译器会给出更严格的警告,指出a未初始化。而编译器的警告可以帮助开发者注意到潜在的错误,从而提高程序质量。

最后,考虑指针的问题。以下代码:

#include 
int main(void) {
int a[5];
printf("%x\n", a);
printf("%x\n", a + 1);
printf("%x\n", &a);
printf("%x\n", &a + 1);
}

假设a的地址是0xbfe2e100,这是在32位机上的值。那么程序会输出什么呢?a的值没有问题,是0xbfe2e100a + 1将被编译器转换为a + 1 * sizeof(int),因为int在32位系统中是4字节。所以a + 1的值是0xbfe2e104。至于&a呢?它是将a转换为指针,属于int *类型,其值的确是0xbfe2e100。但&a本身就是&a[0],因为a是数组,编译器会自动处理这一点。最后,&a + 1的值是0xbfe2e110

这些关于数组和指针的行为可能对你来说有些令人困惑。数组和指针在内存中的表现有很多相似之处,但它们也有显著的区别。数组中的&a实际上是转换为&a[0],这是一个很重要的概念。在代码中使用int *a而不是int a[5]时,a&a的关系是完全不同的。数组将&a视为可变量的大指针,而不是原始指针,因此在处理数组时需要特别谨慎。

这些例子只是冰山一角,C语言还有许多本领需要探索。值得一提的是,这些特性(如不确定性和静态变量的行为)在C++和其他现代语言中已经消失,以提高程序的移植性和一致性。然而,这些特性使得C语言在低层次编程中仍然不可替代。

C语言的设计初衷

尽管C语言已经在动 application,它的设计思路似乎挺有智慧的。Dennis Ritchie设计C语言时,基于以下几点:

  • 信任程序员:不要阻止程序员做他们想做的事。C语言提供了极大的自由度,允许程序员去荒诞。
  • 保持简洁:语言要简单易学,减少开发者的负担。
  • 保证 performance:不管效率如何,都要优先考虑。这一点在处理敏感的嵌入式系统时尤为重要。
  • 今天,许多语言比C要大、复杂且强大,但C语言依然是开发者的首选工具之一。这背后的原因,正是Dennis Ritchie的初衷所在。这门语言的简洁和灵活性,使它在那么多年间从未过时。它的设计理念也为我们提供了宝贵的启示,在实际开发中,您应该从何处下手呢?

    你或许有疑问:“C语言真的那么基础吗?为什么要深入学习一门40年前的语言?”答案是:C语言是所有现代语言的根。每一门高大上的语言都在C的基础上发展。如同学习第一门语言一样,它将让您更加熟悉计算机底层逻辑。

    最后,让我们记住Dennis Ritchie的灵魂。他不仅仅是C语言的发明者,更是编程文化的缔造者之一。正是由于他的努力,使我们得以享受这门语言的魅力。尽管C语言已经发展了,但它的核心思想至今仍在涌现,即如何在性能与移植性之间取得平衡。这将永远是编程领域的重要课题。

    转载地址:http://hzrvz.baihongyu.com/

    你可能感兴趣的文章
    Nginx的是什么?干什么用的?
    查看>>
    Nginx访问控制_登陆权限的控制(http_auth_basic_module)
    查看>>
    nginx负载均衡和反相代理的配置
    查看>>
    nginx负载均衡器处理session共享的几种方法(转)
    查看>>
    nginx负载均衡的5种策略(转载)
    查看>>
    nginx负载均衡的五种算法
    查看>>
    nginx转发端口时与导致websocket不生效
    查看>>
    Nginx运维与实战(二)-Https配置
    查看>>
    Nginx配置Https证书
    查看>>
    Nginx配置ssl实现https
    查看>>
    Nginx配置TCP代理指南
    查看>>
    Nginx配置——不记录指定文件类型日志
    查看>>
    nginx配置一、二级域名、多域名对应(api接口、前端网站、后台管理网站)
    查看>>
    Nginx配置代理解决本地html进行ajax请求接口跨域问题
    查看>>
    nginx配置全解
    查看>>
    Nginx配置参数中文说明
    查看>>
    nginx配置域名和ip同时访问、开放多端口
    查看>>
    Nginx配置好ssl,但$_SERVER[‘HTTPS‘]取不到值
    查看>>
    Nginx配置如何一键生成
    查看>>
    Nginx配置实例-负载均衡实例:平均访问多台服务器
    查看>>