Показаны сообщения с ярлыком Си. Показать все сообщения
Показаны сообщения с ярлыком Си. Показать все сообщения

понедельник, 3 июня 2013 г.

Оптимизация кода - ускорение приложения

Ранее (вернее до вчерашнего дня) я под ускорением приложения подразумевал оптимизацию кода.
Например заместо for(int i=0;i писал for(int i=0;s[i];i++) ...
Ну и так далее, оптимизировал циклы и прочее, часто использовал указатели на char *, нежели пользовался стандартными функциями str.
Но все изменилось вчера, собрал небольшой, но вычислительный код на машинке под Lin64.
Компилил само-собой g++, и скорость на только что свежой тачке с толи с 4, толи 8 ядерным процессором показала практически туже что и на моем рабочем компьютере - винXP32, трехлетней выдержки.

Что за нах? Подумал и поискал в инете, нашел кучу ключей и собрал уже с ключами -m64 -Ofast -flto -march=native -funroll-loops, разница была в десять(10) раз!!!

Вот такая хреновина...

четверг, 13 декабря 2012 г.

Многострочный INSERT в VARCHAR поле

Сегодня портировали базу в IBM DB2, встряли на insert'ах в текстовые поля: varchar'ы и blob'ы, решение простое - нужно квотировать символы #10 и #13 как '||chr(10)||' и chr(13) соотвественно, чтобы весь insert был в одну строку. Без единого разрыва, как говорится

вторник, 25 января 2011 г.

Строки и функции с ними

Как мы уже поняли из предыдущих уроков, строки это всего лишь набор байтов, а точнее массив типа char.

Сегодня рассмотрим функции, которые можно использовать на кошках строках.

Отмечу, что легко написать свои аналогичные функции и они будут выполняться также быстро, как и библиотечные (в этом и прелесть Си), но с другой стороны на какой изобретать велосипед?
Начнем-с.

Длина строки strlen - возвращает количество символов до символа \0. При передаче мусора – программа может выпасть или зависнуть, если будет искать этот самый символ бесконечно.
Возвращает – без знаковое целое число (unsigned int), что в принципе логично – длина строки явно положительная величина.

Копировать строку в строку strcpy. Копирует в заданную строку, другую без ограничения размера первой строки, пока у второй строки не попадется символ \0, символ \0 также копируется. Синтаксис strcpy(v, ”Hello”);

Для копирования определенного количества символов нужно использовать strncpy, где указывается количество копируемых символов, также не контролируется размер исходной строки, синтаксис strncpy(v, ”Hello”, 4), но в этом случае символ \0 не добавляется в конец исходной строки (иными словами strncpy это алиас memcpy и не более того)


Поиск заданного символа в строке strchr - возвращает указатель на символ, либо NULL, если символа увы нет. Синтаксис - v=strchr(str,'a'), где v, указатель на char (char *).

Поиск заданной подстроки в строке strstr - возвращает указатель на первый символ в строке в случае успеха, а в случае неудачи возвращает… NULL!
Синтаксис: v = strstr(str, "Hello");

Сравнение строк strcmp - возвращает 0 в случае полного сравнения, 1 если первая строка больше, -1 если меньше. Больше и меньше – это конечно по ASCII-коду символа, который не совпал. Удобно для сортировки.
У функции есть более прогрессивный потомок strncmp, который также сравнивает строки, но только до определенной позиции(длины).
Синтаксис: strncmp(str, "hello", 4) - т.е. добавилось только количество сравниваемых символов.

Даже не знаю, что добавить. Хотя, помню когда я пришел в Си из Бейсика мне часто не хватало функции MID, или substr.

Ответ пришел очевиден – допустим нужно сравнить строку на совпадение трех символов, начиная с второго как 'ID:', а последующие 4 тогда определить как число. Исходная строка: xID:0034User
В Си if ( (v = strnstr(str+1,’ID:’))) {strncpy(temp,str+4,4);temp[4]=0;id=atoi(temp);}

четверг, 30 декабря 2010 г.

Структуры в C (С, Си)

Привет, с вами опять я - кодер -бывший студент
Иногда есть необходимость сгруппировать некоторые данные, допустим вы делайте программу, которая подбирает лучшую девушку 2011.

Конечно можно использовать ряд массивов, например char NameGirl[10]={"Sveta","Anja",....};int AgeGirl[10]={21,23,...} и т.д.

Но лучше использовать структуры.
Структура struct это объеденение нескольких переменных разных или однотипных в одну пачку.

Синтаксис
struct уникальное_название_вашей_структуры
{
переменные, например int age;
};

Но я не рекомендую использовать такой синтаксис, более простой способ это использовать typedef, описание структуры будет в этом случае, таким:
typedef struct
{
переменные, например int age;
} уникальное_название_tp

В чем разница? В объявлении (в функциях и как переменные),
в первом случае вы должны будете писать struct уникальное_название_вашей_структуры имя_переменной,
во-втором случае всего уникальное_название_tp имя_переменной;

Допустим:
typedef struct
{
char name[30];
short age;
char virgin;
char like_drink;
char emo;
} TS_Girl;

В Си, нет булевых типов переменных (они есть только в C++ - bool), потому используем char, но для кода на самом деле все равно, булевая переменная из C++, занимает в памяти также 1 байт, а не 1 бит - как кажется.

Теперь TS_Girl - это, что-то типа переменной и можно сделать так:
TS_Girl girl_from_work[10];
Тут мы объявили массив структур, обращение к структуре будет выглядеть так:
if (girl_from_work[0].age > 30) continue;

Обращение к членам структуры - через точку TS_Girl wounder_girl;
if (wounder_girl.emo != 0) printf("Не повезло!\n");


В чем прелесть структур - что (кроме группировки) - что данные хранятся в одном месте и последовательно друг-за другом.
Системная функция sizeof вернет размер струтуры, в моем случае sizeof(TS_Girl) вернет 35.
Можно эти данные копировать/записовать как область памяти, например write(file,(char*)&girl_from_work[i],sizeof(TS_Gril));

В некоторых (поздних)версиях С, в структурах появились функции - но это язычество, сын мой, избегай этого!
Есть классы (в C++), а есть структуры и не стоит мешать мух с котлетами.

понедельник, 20 декабря 2010 г.

Циклы и условия

Объединил их в один урок, т.к. на самом деле - это одно и тоже - проверка условия и выполнения оператора или блока операторов.
Разница только в том, один раз выполняется блок/операнд или несколько - по условию.

Условия



Как и в множества других языках - блок проверки условия называется if - в переводе если.

После if пишется условие в круглых скобках, а затем один операнд (с замыкающей ;), либо блок (в фигурных скобках).

Например:
if (age > 21) printf("You may'be buy VODKA\n");

Комбинирование условий



В блоке проверки условий можно проверять не одно, а несколько условий - написано в некоторых учебниках и самоучителей.
Это все вранье и чушь! - В блоке if проверяется только одно условие, а операции && - и, и || - или - это операции над значениями.

Допустим if (age > 21 && sex == SX_MAN) на самом деле сначала выполняется решение условия в скобках, все операции имеют разный приоритет (из школы вам известно что умножить приоритетнее, чем сложение), а у операций && и || - самый низкий приоритет.

Выполняется первая часть (проверка идет справа налево) проверка на пол и ее значение запоминается, затем идет проверка на возраст и ее значение запомниается (обе части получают значение true или false), а далее выполняется третья операция && (и) - и если обе части true - то и общее значение будет true.

Если нужно выполнить разные блоки при срабатывание и не срабатывания условия, то используют конструкцию if () {...} else {}
else в переводе иначе.
Логично - сделай так если условие и иначе если не так.

Циклы


При реализации алгоритма цикла можно использовать любой способ т.е. любую запись, и самое главное при любом случае - в машинных кодах получится одно и тоже, но вот для более понятного исходников их делят:

for - для


Цикл for чаще всего используется там, где нужно выполнить заранее известное количество иттераций (циклов).

Синтаксис
for(условия иницилизации ; блок проверки ; блок выполняемый при каждом цикле - блок шага) {...}
Вместо блока операндов, можно также писать один операнд - если это необходимо.

Пример:
for(i=0;i<10;i++) printf("i=%d\n",i);
В примере в блоке условия инициализации - переменной i назначается значение = 0.
В блоке проверки переменная i проверяется, что она (переменная) меньше 10, в блоке шага - переменная i увеличивается на один.

Блок инициализации выполняется всегда - а блок шага только в том случае, если выполняется условия.
Т.е. выполняется i=0; затем проверяется i<10, далее выполняется блок за for - печатается строка printf("%d\n",i), затем выполняется блок шага - i++,
затем опять проверяется условие и если оно = true (меньше 10 в нашем случае) - опять выполняется блок за for и блок шага. И до тех пор пока блок условия - будет = true.

В любом блоке for можно указать несколько действий через запятую, например:
for(i=0,j=0; i<20,j<5; i+=2,j++) printf("i=%d,j=%d\n",i,j);
Т.е. в блоке инициализации выполняем присвоение перменной i и переменной j.

В блоке проверки условий выполняется два условия что i меньше 20 и j меньше 5 - на самом деле цикл будет только при выполнении всех условий - аналогично сработает (и кстати более лучше для понимания) i<20&&j<5
В блоке шага, выполняется также два операнда, увеличение переменной i на два и i на один.
Результат:
i=0,j=0
i=2,j=1
i=4,j=2
i=6,j=3
i=8,j=4


Скажу больше в блоках можно использовать даже функции, например for(i=0,printf("start");i<10;i++,printf("step\n")) printf("i=%d\n",i);

Но кроме использования их в блоках инициализации и проверки - не вижу смысла использовать функции в блоке шага, уж лучше перенести в блок за for, либо использовать....

while - пока


Цикл while - чаще используют если количество иттераций заранее не известно.

Синтаксис:
while (условие) {...}
Блок за while будет выполнятся пока условие = true.

Если условие изначально false то блок не будет выполнятся вообще, если это необходимо используют более редкую конструкцию while с do
Синтаксис:
do {...} while(условие);
Блок do-while выполнится один раз как минимум и будет выполнятся пока условие = true.


Для изменения работы циклов (for и while) - есть ключевые слова break и continue

break - сломать


Цикл прерывается и выполняется слелующий операнд за циклом

continue - продолжить


Цикл принудительно переходит на блок проверки условия, однако у for - выполняется блок шага


Еще про проверки условий



Еще для более удобного представления множественной проверки условий используют конструкцию switch - переключатель
Синтаксис:
switch(выражение)
{
case значение1:
...
break;
case значение2:
...
break;
default:
...
}


в условиях стоит выражение, значение которого проверяется в каждом case и если оно совпадает с одним из значением то выполняется операнды под этим case.
Внимание, если не будет стоят break то затем будут выполнятся все другие условия, под другими case, которые описаны дальше.

Блок default выполняется как глобальный else - если не один case не подошел.

Иногда специально не ставят break для выполнение какого-то кода.
Вообще switch как и циклы - это все проверки условия (if) и переходы на строку (goto)

Однако использовать goto - некомильфо и презирается гуру.
Однако если у вас вложенный тройной цикл - то плюйте на гуру и пишите goto, между прочим в коде под Linux для обработки изображений jpeg используется goto, так что - вообще нежелательно - но если очень хочется - то можно.

понедельник, 29 ноября 2010 г.

Указатели, функции и указатели на функции (С, Си)

Итак, в уроке №2 мы разобрали какие бывают переменные и как они хранят свое содержимое.
Всё в мире байты - это как эпиграф.

А байты хранятся в памяти, в виде цепочки - друг-за-другом, чтобы узнать где именно - можно получить указатель на адрес переменной.
Вообще разница между адресами и указателями очень тонка, и отличия возникают все же больше в C++, нежели в простом C. Но об этом позже


В языке C, получить адрес переменной - знак амперсанда - &.

Допустим:int a=5;int *b=&a;
Переменная b - на самом деле указатель на целочисленный тип (int). Чтобы точнее это понять стоит писать int* b, но все пишут (и я тоже) как int *b, потому что в пробелы и их отсутствие - это не суть.

Так вот, переменная b указывает на адрес памяти переменной a или если кратко - b указатель на a.
Для понимания можно выполнить следующий код *b = 6;, звездочка перед b означает действие разыменование, и реально именно переменная a станет равной 6!
Если же выполнить конструкцию просто b = 6; то в результате переменная b будет содержать на область памяти с смещением 6. Само по себе это ничего не даст (и ошибок тоже), но если после этого сделать *b = 6; 99% что ваша программа закроется с ошибкой. Потому что вы меняете значения памяти не глядя, а там может располагаться исполняемые код или другие данные.

Это тоже одна из плавающих ошибок, которую трудно найти.

Функции


Функции это самостоятельный код, который объеденен в группу - функцию.
Функция может принимать любое количество, любых типов переменных и возвращать любой тип переменных. А может и не возвращать - в некоторых языках делят на функции и процедуры - мол процедуры не возвращают ничего, в C - функции могут возвращать тип void - ничего/пусто.

Синтаксис очень просто перед именем функции пишут тип возвращаемых данных, например int, затем имя функции, а в круглых скобках перечисления через запятую типы аргументов и их имена.
Например int my_func(int a,int b,double num)

Тело функции пишутся после объявления функции в фигурных скобках, например
int my_func(int a,int b,double num)
{
int c = a + b;
c = c + (int)num;
return c;
}

Ключевое слово return говорит что в данном месте нужно прекратить выполнение функции и вернуть значение, которое у него указано (в примере - переменная c).
Если функция типа void - то можно конструкция упрощается просто return;.

Теперь про большие проекты, функции используются еще для того чтобы разнести код по его смыслу на разные модули.
Чтобы в другом файле(модуле) использовать функцию другого файла(модуля) нужно написать(указать) ее прототип.

Например в нашем случае int my_func(int a,int b,double num); - описания прототипа заканчивается точкой запятой, а описание функции - телом функции в фигурных скобках.

В прототипе функции(описании) можно не указывать названия переменных или (если хочется) писать любые имена - все равно комплиятор их не смотрит

int my_func(int,int,double); - это тоже прототип функции.

Забегая вперед скажу, что в C++ можно не указывать имя переменной и в описания функции - это означает, что в данной функции вы не используете эту переменную, а ее синтаксис сохранен для совпадения с чем-то. В этом случае компилятор не выдает предупреждение(warning) Parameter 'a' is never used in function

Допустим мы в функции my_func изменем содержание переменной a, например a = 5;, то в сама переменная указаная в функции при вызове - не изменится.

Например:
int my_func(int a,int b,double num)
{
a = 6;
int c = a + b;
return c + (int)num;
}
int main()
{
int i=5,j=6,n;
double k=1;
n=my_func(i,j,k);
printf("n=%d, i=%d\n",n,i);
}
Выдаст n=13, i=5, потому что в функцию передаются значения переменных, а не их адреса

Чтобы изменять значения переменной нужно в описании функции указывать не просто переменную, а указатель на нее.
void my_func2(int *a,int b)
{
*a = b + 5;
}
int main()
{
int i=5,j=6;
my_func2(&i,j);
printf("i=%d\n",i);
}

Код вернет i=11, т.к. в функции мы используем указатель на переменную, и пусть она называется не так как в основной функции (там i, а в myfunc2 - a) - имена не играют никакой роли, все равно в область памяти переменной указанной в качестве первого аргумента, будет записано значение переменной второго аргумента + 5.

С переменными более, менее ясно - теперь - как передавать массивы, в частности строки?


Допустим мы хотим написать функцию, которая будет определять сколько букв 'а', в заданной фразе.
Нам нужно передать в функцию строку - массив символов, так как это сделать?
Очень просто, в описании функции нужно написать, что ожидается указатель на элемент массива (в нашем случае - символ).
Например: int getSymA(char *src);
В функции, чтобы получить элемент массива, можно также просто писать str[1] или *(str+1) - это два идентичных кода вернут второй символ в строке.

В целом наша функция будет выглядеть так:
int getSymA(char *src)
{
int i,n=0;
for(i=0;src[i];i++)
{
if (src[i]=='а') n++;
}
return n;
}

кстати, фигурные скобки после циклов, если код состоит из одной строки можно не указывать, например:
int getSymA(char *src)
{
int i,n=0;
for(i=0;src[i];i++)
if (src[i]=='а') n++;
return n;
}

или вообще не использовать лишние переменные:
int getSymA(char *src)
{
int n=0;
for(;*src;src++)
if (*src=='а') n++;
return n;
}

Это как эволюция понимания, потому рассмотрим последний код построчно.
  1. Первая строка - говорим что нужно создать переменную n и инициализировать ее нулем.

  2. Вторая строка - цикл for - пропускаем блок инициализации (первый после круглой скобки) сразу ставим точку с запятой, второй блок - условия - в нем проверяем является ли указатель на символ 0 - пустым символом, означающим конец строки, третий блок перемещаем указатель в памяти (указатель, а не его значение)

  3. Третья строка - проверяем, а текущий указатель указывает на букву А? Если так увеличиваем счетчик n на один, если нет - то ничего не делаем (нет блока else)


Сделаем вызов, скажем следующего кода:

char s[10]="Hello!";
printf("%d\n",getSymA(s));


Первое - при передаче массива, не надо указывать его адрес - нужно указывать саму переменную, т.к. она и есть уже указатель на переменную.
Второе -
АААА! Мы изменяли указатель! Программа сломается и нельзя использовать переменную дальше! - Вовсе нет, мы передали указатель, который может менять значение переменной, но мы его не модифицировали, а просто изменяли его адрес.
Чтобы модифицировать сам указатель нужно использовать указатель на указатель :)

Код:
void hhh(int **a,int *b)
{
*a = b;
}
int main()
{
int i=5,j=6,*n=&i;
hhh(&n,&j);
printf("i=%d,j=%d,n=%d\n",i,j,*n);
}

результат: i=5,j=6,n=6, в функцию мы передали указатель на указатель переменной i, но в самой функции сказали, что теперь он равен указателю на переменную b.
Сами переменные (i и j) не изменились, а вот указатель n, ранее указывающий на i, стал указывать на переменную j.

И отвечая на ваш вопрос скажу - ДА! Есть указатели на указатели указателей, особенно это любит использовать корпорация Microsoft
Но в реальной жизни, хватает и указателей на указатели. А некоторым и просто указателей.

Указатели на функции


Раз в функции можно передать указатель на область памяти, то почему не передать на ту область где находится функция?
Конечно можно и не так сложно, код:
#include
int pw(int n,int t)
{
int i,r=1;

for(i=0;i>t;i++)
r*=n;
return r;
}
int hhh(int (nn(int,int)),int z)
{
return nn(z,2);
}
int main()
{
printf("%d\n",hhh(pw,3));
}


Вызываем функцию hhh, передавая ей в качестве аргументов адрес на функцию и число.
В функции hhh вызываем функцию указанную в качестве параметра и получаем в результате цифру 9.

Для сложных функций, передаваемых в качестве параметра, лучше использовать заменитель typedef
typedef int (*load_func)(int,int);
int hh(load_func f,int z)
{
if (f) return f(z,2);
return 0;
}
int main()
{
printf("%d\n",hhh(&pw,3));
}


понедельник, 22 ноября 2010 г.

Второй урок - Типы переменных. (С, Си)

Данный урок, продолжение первого урока.
Раберем типы переменных в C.
Их не много, это символ, он же байт - char принимает значения от 0..255 (или -127 .. +127)
Целочисленное - int - значения целые числа
С плавающей точкой - float
С двойной точностью - double
Тип данных float практически не используется в современном программировании.

Существуют несколько префиксов к типам данных, например знаковость это signed и unsgined.
По умолчанию переменные знаковые (signed), хотя такая запись не будет ошибочной signed int a

В чем различия? В диапазоне принимаемых значений в случае знака это от - до +, в беззнаковом случае(unsigned) от 0 до максимального значения.

Максимальное значение для char это 255 (емкость - 28) , а для int (емкость 232) - в 32 битных системах, в 64 - соотвественно 264, а я помню еще времена когда в int можно было загнать только до 216.

Размер памяти выделяемый для каждой переменной, естественно кратный ее максимальному значению (или наоборот - тут спор как про яйцо и курицу), для char - 1 байт, для int (32) - 4 байта.

Для float - 4 байта, для double - 8 байт.

Также есть префиксы изменяющие размер данных - это short и long
Но не все варианты возможны, скажем для char и float нельзя указывать такой префикс.
Можно только для int и double. Т.к. long float это и есть double, а long char - это наверное int.

Так вот short int - это слово (два байта), т.е. максимальное значение 216
Для long int в bcc и gcc - размер равен что и просто int - двойное слово (четыре байта).
Однако для некоторых компиляторов - long int это int64 - 8 байт.

Вариантам short double тоже несуществует, однако есть long double.
Кстати в разных компиляторах это разные размеры данных. В bcc - 10 байт, а в gcc - 12 байт (на 32битной платформе).

По поводу signed/unsigned - тут стоит заметить что переполнение емкости данных, ведет к разным значениям в зависимости от signed.
Например unsined char a = 255; a+=2, "a" будет равна 1. А char a = 127; a+=2, "a" будет равна -127.

Эта нехитрая ошибка приводит к большому количеству багов и глюков множества программ.

А как же строки, спросит тот редкий читатель, что увидел этот текст.
Все просто - строка это массив символов. Размерность массива указывается в квадратных скобках после имени переменной
Например int a[50]; - целочисленный массив, с 50 элементами. Для доступа к конкретному элементу в квадратных скобках указывается его индекс int b= a[5];.
Массивы можно инициализировать сразу при описании, например int a[5] = {1,2,3};, значения указывается через запятую в фигурных скобках. Значений может быть меньше чем размерность массива, но не больше!

Также и строки char str[10] = {'H','e','l','l','o','!',\0};.
Символы указывается в апострофах, и в ковычках может быть только один символ. char a='aa';//ошибка!!!

Но гораздо легче и проще инициализировать строки (а также использовать их для других целей) через строку заключенную в двойные кавычки char str[10] = "Hello!";.
Следует заметить, что 0-символ ставится автоматически.
Т.е. char str[6]="Hello!"; - не сработает, скажет что размер массива меньше чем количество инициализируемых элементов.
Т.е. "Hello!" автоматически преобразуется в {'H','e','l','l','o','!',\0}


вторник, 16 ноября 2010 г.

Первый урок - Hello world на C(си)

В языке C очень малый набор ключевых слов, т.к. только несколько типов переменных, слова управления циклами и функциями.
Еше необходимо заметить, что C язык компилируемый, т.е. для его выполнения необходимо его скопилировать - преобразовать из текста языка в машинный код.

При выполнении программы, написаной на C необходимо описать главную функцию main, которая и будет вызываться при запуске.
Функция main является такой-же обычной функцией как и все остальные, в целом у нее есть два несколько прототипов описания.
void main(), int main(), int main(int,char **) и void main(int,char **).

Первое слово перед main это тип функции - тип значения которое она может веруть int - целочисленное число, void (пусто) ничего.
Затем идет имя функции (в нашем случае main), а далее в круглых скобках - принимаемые параметры, также сначала тип, а затем название переменной.
Причем как в описании, так и в самой функции названия могут отсутствовать.
Отсутствие название переменных и для чего это - обсудим потом


Тут нужно учесть, что компилятор gcc считает что функция main должна обязательно иметь тип int, для того чтобы вернуть код выполнения программы, а вот скажем bcc от Borland допускает и тип void, хотя в этом случае на самом деле возвращает 0.
Чтобы поддерживать общие стандарты лучше описывать функцию main все-же через тип int

Функция main отличается от других библиотечных функций тем, что она противоположная по смыслу, в остальных функциях вы знаете их прототип для вызова, но не знаете/видете код функции, а для main все как раз наоброт - компилятор знает ее описание, но вот код - должны написать вы.

И так самый примитивный рабочий код на C!
int main()
{
return 0;
}

Данный код можно скомпилировать в программу и даже выполнить - правда, он ничего не выполнит, потому как единственная строка кода в нем return 0; - выйти из функции и вернуть значение 0.
Ну да в C - разделитель комманд точка с запятой ';'


Для того. чтобы что-нибудь вывести на экран нужно вызвать функцию для вывода, впрочем большинство стандартных функций уже давно написаны. Например для вывода на экран(консоль) есть специальная библиотека stdio, которая содержит тьму функций.
Но нам интересна пока только одна - printf - форматированный вывод на экран.
Чтобы подключить библиотеку нужно указать директиву include с указанием имени библиотеки, имя библиотеки можно написать в угловых скобках (тогда компилятор библиотеку будет искать в стандартной папке, либо в двойных ковычках тогда он сначала ищет в текущем каталоге, а потом в стандартом)
Все директивы - это команды к препроцессору, т.е. процессу вызываемому до компиляции, это облегчает жизнь программистам, в противном случае им пришлось бы самим писать все прототипы функций вручную.
Ну и директивы пишутся через знак #, всего их не очень много основные: include, define, ifdef, else, error

Для нашего примера это #include <stdio.h> - т.е. включить заголовки(описания прототипов) от библиотеки stdio (расширение .h - как раз указывает что это заголовки от англ. header)

Функция printf поддерживает множество аргуметов, но пока нас интересует только первый - а именно базовая строка, которая будет выводится на экран.
Cамый примитивный рабочий код на C с выводом на экран!
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}



Т.к. в качестве первого аргумента функции printf является строка, то приходится квотировать некоторые символы, например \n - означает перевод строки, а \" - двойную кавычку


Теперь скомпилировав и выполнив это программу, увидем на экране Hello, World

Если, вы конечно, запускаете ее из консоли (в винде через cmd), а в линуксе через терминал.

Ну и чтобы скомпилировать программу - команда gcc hello.c -ohello.exe - это если у вас gcc