Российский государственный гуманитарный университет
Опубликован: 15.08.2003 | Доступ: свободный | Студентов: 6518 / 1489 | Оценка: 3.91 / 3.79 | Длительность: 06:55:00
Лекция 4:

Введение в программирование скриптов на C

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >

Аргументы командной строки

Аргументы командной строки передаются в С-скрипт через второй параметр главной процедуры. Второй параметрмассив указателей на строковые константы, которые и есть аргументы командной строки. Число таких аргументов определяется первым параметром главной процедуры. В этом смысле просмотреть все аргументы командной строки можно в цикле for:

#include <stdlib.h>
#include <stdio.h>
main(argc,argv,env)
int argc;
char *argv[];
char *env[];
{
int i;
printf("Content-type: text/plain\n\n");
for(i=0;i<argc;i++)
{
printf("argv[%d]=%s\n",i,argv[i]);
}

}

В данном случае скрипт генерирует простую текстовую страницу, на которой в столбик распечатываются аргументы командной строки скрипта. Такие аргументы появляются только у запроса типа ISINDEX. При работе с числовыми аргументами нужно помнить, что передаются они в программу как строки, и их следует преобразовывать в числа. Лучше всего это делать при помощи функций atoi.

Стандартный поток ввода

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

С каждым файлом при открытии потока данных связан дескриптор файла, который, являясь совокупностью данных о потоке, описывает поток. Со стандартным потоком ввода в С связывают дескриптор с именем STDIN. Во многих случаях это имя указывать не надо, т.к. оно предполагается по умолчанию.

Самый простой способ чтения потока стандартного ввода обеспечивает функция посимвольного чтения getc():

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
void main(argc,argv,env)
int argc;
char *argv[];
char *env[];
{
char *query;
int length;
length = atoi(getenv("CONTENT_LENGTH"));
query = (char *) malloc(length+1);
memset(query,'\000',length+1);
for(int i=0;i<length;i++)
{
query[i] = getc();
}
free(query);
}

Функция getc() доставляет по одному символу из потока стандартного ввода. Не следует думать, что это медленный способ чтения. Во-первых, сервер передает данные через канал (pipe), а во-вторых, поток буферизуется. Поэтому даже посимвольное чтение данных происходит достаточно быстро.

Можно прочитать и все данные сразу. Для этого пользуются функцией fread():

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
void main(argc,argv,env)
int argc;
char *argv[];
char *env[];
{
char *query;
int length;
length = atoi(getenv("CONTENT_LENGTH"));
query = (char *) malloc(length+1);
memset(query,'\000',length+1);
fread(query,length,1,STDIN);
free(query);
}

В данном случае мы читаем из потока стандартного ввода STDIN в буфер query ровно length символов.

Последнее замечание. В наших примерах используется функция malloc(). Эта функция отводит память под данные, которые мы считываем со стандартного ввода. После завершения скрипта нужно обязательно освободить эту память, не уповая на то, что после завершения программы вся память все равно освободится. Здесь учитывается два момента. Во-первых, использование такого способа размещения данных связано с попыткой избежать переполнения при использовании областей памяти фиксированной длины, которое приводит к аварийному завершению программы. Во-вторых, при переходе от CGI-скриптов к FastCGI-скриптам это позволяет избежать "утечки" памяти. В FastCGI скрипт не завершается после ответа клиенту, а остается "висеть" в памяти в ожидании следующего запроса. Если память не освободить, то при новом обращении произойдет резервирование новой области памяти. В конечном итоге свободной памяти может и не оказаться.

Типы данных и переменные

В языке С определено несколько типов данных, которые отражают архитектуру большинства современных компьютеров: целые числа (короткие и длинные), вещественные числа (нормальной и двойной точности), символы, массивы, структуры, кучи и указатели. Если программировать сложные CGI, то набор всех этих типов данных будет затребован программистом, но для программирования простых скриптов вполне достаточно рассмотреть короткие целые числа, строки и указатели.

Любая переменная в С должна быть продекларирована. В противном случае при выполнении многих операций можно получить неожиданные результаты. Для каждого типа данных используется своя форма декларации переменной. Для аккуратной работы с типами данных в С следует использовать операцию преобразования данных. Последнее особенно актуально при работе с указателями и числовыми данными, в которых можно потерять точность.

Целые числа

Целое число декларируется как:

int a;
unsigned int au;
short b;
long c;

Нас интересуют только первые две строки. Последняя строка декларирует длинное целое число. Короткое число попадает в интервал -214<a<214. Если у числа указан модификатор unsigned, то оно попадает в интервал 0<a<215.

Одновременно с декларированием число можно проинициализировать, что вообще-то рекомендуется делать всегда:

int a=0,b=0;

Как видно из этого примера, в одном операторе декларирования (объявления) переменных можно указать сразу несколько переменных одного типа и при этом их можно инициализировать. Переменные целого типа необходимы в CGI-программировании при обработке обращений по методу POST. Для того, чтобы считать данные из потока стандартного ввода, нужно указать скрипту, сколько байтов оттуда следует считать. При этом сначала текстовую константу из переменной окружения CONTENT_LENGTH следует преобразовать в число, а затем использовать в операторах чтения или цикла:

#include <stdio.h>
#include <string.h>
void main()
{
char *length,*buf;
int n,i;
length = (char *) getenv("CONTENT_LENGTH");
n = atoi(length);
buf = (char *) malloc(n+1);
memset(buf,'\000',n+1);
for(i=0;i<n;i++)
{
buf[i] = getc();
}
printf("Content-type: text/plain\n\n%s\n",
        buf);
free(buf);

Функция getenv() позволяет получить значение переменной CONTENT_LENGTH, а функция atoi() — преобразовать это значение в целое число, которое потом используется в качестве границы при посимвольном чтении данных из стандартного потока ввода.

Строки символов

В С нет специального типа данных, который позволял бы работать со строками символов. Для этой цели используются массивы символов. Одиночный символ или массив символов можно объявить через оператор char:

char a='\000';
char buf[];
char buf[20];

В первом случае переменная a — это просто одиночный символ. В зависимости от реализации компилятора на него будет отводиться разное число байтов. В наиболее экономичном варианте — 1 байт, если символ отображается на короткое целое — 2 байта, если в архитектуре аппаратной платформы нет числа меньше четырех байтов — на четыре байта. Одним словом, не следует думать, что под символ всегда отводится 1 байт.

Символы можно использовать в арифметических операциях:

#include <stdio.h>
#include <stdlib.h>
void main()
{
unsigned char a='a';
a++;
printf("%c\n",a);
}

В результате этих нехитрых операций вместо "a" будет напечатано "b". Код символа рассматривается как целое число и увеличивается на 1. Этот принцип можно использовать при преобразовании строчных латинских букв в заглавные (с буквами русского алфавита так не получится).

Определив массив символов buf[20] в 20 символов длиной, мы зарезервировали под него место, в которое можем разместить 20 символов. При объявлении buf[] мы только обозначаем, что будем использовать переменную buf для обращений к массиву символов. Места под сам массив мы не отводим. То есть место мы отвели, но под указатель — переменную, которая будет хранить адрес массива символов. Следовательно, buf[20] отводит место под массив и под указатель на него.

Однако, как же быть со строками символов? Ведь все переменные окружения — это строки символов. Для работы с ними используются функции, которые описаны в include -файле string.h:

strcmp() — сравнение строк
strcpy() — копирование строк
strstr() — поиск подстроки
и т.д.

При этом строка распознается по символу '\000' в конце строки, т.е. все разряды байта или совокупности байтов, в которой расположен символ, равны нулю. Приведем пример копирования QUERY_STRING в массив символов query:

#include <stdio.h>
#include <string.h>
void main()
{
char query[1024];
strcpy(query,getenv("QUERY_STRING"));
printf("Content-type: text/plain\n\n%s\n",
        query);
}

Функция strcpy() копирует строку запроса из переменной окружения QUERY_STRING в переменную query и после нее дописывает символ '\000'. Мы заранее отвели побольше символов под массив query, т.к. strcpy не проверяет границ массива, и при такой операции можно запросто "наехать" на область памяти, не отведенной под наш скрипт, что приведет к его аварийному завершению (segmentation violation — как это знакомо). Поэтому лучше контролировать размеры буферов и использовать указатели.

Указатели

Указатели — это наиболее мощное и одновременно опасное средство программирования в С. В современных языках, таких как Java, например, указатели уничтожают как класс, т.к. именно они — основной источник множества ошибок, а, точнее, манипуляции с ними.

С другой стороны, нельзя написать эффективной программы (быстро исполняется и занимает мало памяти), если не использовать указатели и адресную арифметику, которая позволяет манипулировать указателями.

Указатель на переменную определенного типа данных объявляется путем ввода символа "*" перед именем переменной:

int *n;
char *query;

При этом место резервируется под адрес соответствующего значения. В Intel-платформах существуют модификации указателей в зависимости от модели памяти (т.е. какой длины адрес должен использоваться — 16 бит, 24 бита или более). В 64-разрядных архитектурах просто указывается опция компилятора (например, в Irix 6.4 "-64" — длинные адреса для всей программы или "-32" — короткие адреса).

С указателями при программировании CGI-скриптов приходится сталкиваться постоянно. Указатель на переменную окружения возвращает функция getenv(). Другими словами, она возвращает адрес начала значения переменной окружения:

char *length;
length = (char *) getenv("CONTENT_LENGTH");

Другой пример — указатель на массив переменных окружения и массив аргументов командной строки:

#include <stdlib.h>
#include <stdio.h>
void main(argc,argv,env)
int argc;
char *argv[];
char *env[];
{
/* тело программы */
}

В данном случае конструкция типа *argv[] — это массив указателей, которые указывают на символы, а точнее, на символьные переменные. В данном контексте совсем по-другому смотрится конструкция char a[] — это просто иная форма записи указателя. Переменная a — это указатель на массив. Есть, правда, один нюанс. Он заключается в том, что, обращаясь к элементам массива, перемещаться мы будем на длину элемента массива, а это уже зависит от типа данных.

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >